Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

verify config: Playbook verify_config.yml included in site.yml #385

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.env/
*.log
ansible.cfg
__pycache__
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,21 @@ This requires at least k3s version `1.19.1` however the version is configurable

If needed, you can also edit `inventory/my-cluster/group_vars/all.yml` to match your environment.

#### verify routing prefixes and ip addresses in all.yml
Ensure that the ip routing prefix matches the the one from the flannel_iface (`eth0` by default) for each host.
Example `192.168.30` is the default routing prefix, and it is used in the following variables:
- apiserver_endpoint
- metal_lb_bgp_peer_address (commented by default)
- metal_lb_ip_range
Also Ensure that the apiserver_endpoint is not in the metal_lb_ip_range

For your convience The playbook site.yml verifies the above elements of the config are valid.
* Optionally to skip these verifications set, in all.yml `verify_config: false`
* Optionally to just verify the config Run
`ansible-playbook -i inventory/my-cluster/hosts.ini verify_config.yml`
* Optionally manually recover ip routing prefix based on inet and netmask from
`ansible -i inventory/my-cluster/hosts.ini --become -m shell -a 'ifconfig eth0 | grep "inet "' all`

### ☸️ Create Cluster

Start provisioning of the cluster using the following command:
Expand Down
2 changes: 2 additions & 0 deletions inventory/sample/group_vars/all.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,5 @@ custom_registries_yaml: |
auth:
username: yourusername
password: yourpassword

verify_config: true
13 changes: 13 additions & 0 deletions roles/verify_config/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
# To be ran on localhost after verify_config_gather role.
# Verifies gatherd facts.
- name: Collect all routing4_prefixes into a list
set_fact:
all_routing4_prefixes: "{{ groups['all'] | map('extract', hostvars, 'routing4_prefix') | list }}"
routing4_prefix: "{{ groups['all'] | map('extract', hostvars, 'routing4_prefix') | list | first }}"

- name: Ensure all hosts have the same routing4_prefix
assert:
that: all_routing4_prefixes | unique | length == 1
fail_msg: "Not all hosts have the same routing4_prefix."
success_msg: "Using verified routing prefix {{ routing4_prefix }} across all hosts"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we want to include this binary?

Copy link
Author

@edoziw edoziw Oct 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, I missed that, I thought __pycache__ was in the .gitignore
I'll fix .gitignore and also git rm --cached <the file> then push

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be ok now.
Sorry about that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

Binary file not shown.
53 changes: 53 additions & 0 deletions roles/verify_config_gather/filter_plugins/range_to_ips.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import netaddr

''' returns True if ip is in range
examples:
- see test functions below
'''
def test(ip, range, expected):
assert netaddr_ip_in_dash_range(ip, range) == expected

def test_netaddr_ip_in_dash_range():
# ipv4
test('192.168.1.1', '192.168.1.1-192.168.1.2', True)
test('192.168.1.1', '192.168.1.1', True)
test('192.168.1.1', '192.168.1.2-192.168.1.3', False)
test('192.168.1.1', '192.168.1.2', False)

# ipv6 style
test('::ffff:192.168.1.1', '::ffff:192.168.1.1-::ffff:192.168.1.8', True)
test('::ffff:192.168.1.1', '::ffff:192.168.1.1', True)
test('::ffff:192.168.1.1', '::ffff:192.168.1.2-::ffff:192.168.1.8', False)
test('::ffff:192.168.1.1', '::ffff:192.168.1.2', False)

# Note I expedted true but apperently the netaddr library does not support this?? or I don't understand ipv6 :)
test('::2:1', '::2:1-::2:2', False)
'''
todo: ?
- netaddr_ip_in_dash_range('192.168.1.1', '192.168.1.0/24') => True (TODO: test, implement)
- netaddr_ip_in_dash_range('192.168.99.1', '192.168.1.0/24') => False (TODO: test, implement)
'''

def netaddr_ip_in_dash_range(ip, range):
# return False early if range is invalid
if '-' not in range:
ip_start = range
ip_end = range
else:
ip_start = range.split('-')[0]
ip_end = range.split('-')[1]
return ip in [str(ip) for ip in netaddr.iter_iprange(ip_start, ip_end)]


class FilterModule(object):
''' Ansible filters. Interface to custom netaddr methods.
https://pypi.org/project/netaddr/
'''

def filters(self):
return {
'netaddr_ip_in_dash_range' : netaddr_ip_in_dash_range
}

if __name__ == '__main__':
test_netaddr_ip_in_dash_range()
117 changes: 117 additions & 0 deletions roles/verify_config_gather/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
---
# To be ran on all hosts before role verify_config.
# Gathers facts and ensures they are set

- name: Set routing4_prefix from regex
set_fact:
routing4_prefix: "{{ hostvars[inventory_hostname]['ansible_' ~ flannel_iface]['ipv4']['broadcast']
| regex_replace('\\.?255', '') }}"
routing6_cidr: "{{ hostvars[inventory_hostname]['ansible_' ~ flannel_iface]['ipv6'][0]['address'] }}/{{
hostvars[inventory_hostname]['ansible_' ~ flannel_iface]['ipv6'][0]['prefix'] }}"

- name: Check if fact routing4_prefix exists and is not empty
assert:
that:
- routing4_prefix is defined
- routing4_prefix is not none
- routing4_prefix != ''
fail_msg: >-
The fact 'routing_4prefix' is not defined, is null, or is empty
(based on flannel_iface: {{ flannel_iface }} ipv4 broadcast).

# metal_lb_bgp_peer_address
- name: Assert that metal_lb_bgp_peer_address starts with routing4_prefix, or is ipv6
assert:
that:
- >
metal_lb_bgp_peer_address.startswith(routing4_prefix)
or metal_lb_bgp_peer_address.matches('.*:.*')
fail_msg: >
The fact 'metal_lb_bgp_peer_address' <{{ metal_lb_bgp_peer_address }}>
doesn't start with the routing prefix <{{ routing4_prefix }}>
when: metal_lb_bgp_peer_address is defined

# metal_lb_ip_range
- name: >
Assert that metal_lb_ip_range (when string) contains <ipv4>-<ipv4>
and both ips start with routing4_prefix, skip any containing ':' (ipv6)
assert:
that:
- metal_lb_ip_range | regex_search('^{{ routing4_prefix }}\.[0-9]{1,3}-{{ routing4_prefix }}\.[0-9]{1,3}$|.*:.*')
fail_msg: >
metal_lb_ip_range <{{ metal_lb_ip_range }}> has one or more ipv4s
that don't start with the routing prefix <{{ routing4_prefix }}>
when: metal_lb_ip_range is string

- name: Assert that metal_lb_ip_ranges (when list) has only strings that match the regexes in the task above
assert:
that:
- >
( metal_lb_ip_range
| select('match', '^{{ routing4_prefix }}\.[0-9]{1,3}-{{ routing4_prefix }}\.[0-9]{1,3}$|.*:.*')
| list | length
)
==
(metal_lb_ip_range
| length
)
fail_msg: >
metal_lb_ip_range <{{ metal_lb_ip_range }}> has one or more values with ipv4s
that don't start with the routing prefix <{{ routing4_prefix }}>
when: metal_lb_ip_range is not string and metal_lb_ip_range is not mapping and metal_lb_ip_range is iterable

# apiserver_endpoint
- name: Assert that apiserver_endpoint is not in metal_lb_ip_range (when string) using network_in_usable
# For /<mask> ranges
assert:
that:
- not ( metal_lb_ip_range | ansible.utils.network_in_usable( apiserver_endpoint ))
fail_msg: "apiserver_endpoint {{ apiserver_endpoint }} cannot be in the metal_lb_ip_range {{ metal_lb_ip_range }}"
success_msg: >
apiserver_endpoint {{ apiserver_endpoint }} is *probably* not in the metal_lb_ip_range {{ metal_lb_ip_range }}
when: metal_lb_ip_range is string

- name: >
Assert that apiserver_endpoint is not in metal_lb_ip_range (when string)
using custom filter netaddr_ip_in_dash_range
# For <ip>-<ip> ranges. Not sure this works for ipv6
assert:
that:
- not (apiserver_endpoint | netaddr_ip_in_dash_range(metal_lb_ip_range))
fail_msg: "apiserver_endpoint {{ apiserver_endpoint }} cannot be in the metal_lb_ip_range {{ metal_lb_ip_range }}"
success_msg: >
apiserver_endpoint {{ apiserver_endpoint }} is *probably* not in the metal_lb_ip_range {{ metal_lb_ip_range }}
when: metal_lb_ip_range is string
# *probably* in the success_msg sections of the prior two tasks because not all cases may work

- name: Assert that apiserver_endpoint is not in metal_lb_ip_ranges (when list) using logic of the task above
assert:
that:
- not (apiserver_endpoint | netaddr_ip_in_dash_range(item))
# this probably fails on an ipv6 range ''::1-::2', and on <ipv4or6>/<mask> ranges
fail_msg: "apiserver_endpoint {{ apiserver_endpoint }} cannot be in the metal_lb_ip_range item {{ item }}"
success_msg: >
apiserver_endpoint {{ apiserver_endpoint }} is *probably* not in the metal_lb_ip_range item {{ item }}
loop: "{{ metal_lb_ip_range | list }}"
when: metal_lb_ip_range is not string and metal_lb_ip_range is not mapping and metal_lb_ip_range is iterable
# these (when string, when list tasks) smell funny.
# It seems there should be a way in one task to handle an object that is a string or a list
# and loop on {{ [metal_lb_ip_range] }} or {{ metal_lb_ip_range }} respectively
# when it is a string it skips each char :(
# I tried the select pattern like in the task
# 'assert that metal_lb_ip_ranges (when list) has only strings that match the regexes in the task above'
# but it didn't work

- name: Assert that apiserver_endpoint, is ipv4 and starts with routing4_prefix, or is ipv6 and is in routing6_cidr
assert:
that:
- apiserver_endpoint is defined
- apiserver_endpoint is not none
- >-
( apiserver_endpoint | ansible.utils.ipv4 and apiserver_endpoint.startswith(routing4_prefix) )
or
( apiserver_endpoint | ansible.utils.ipv6 and apiserver_endpoint | ansible.utils.ipaddr(routing6_cidr))
fail_msg: >
The fact 'apiserver_endpoint' <{{ apiserver_endpoint }}>
doesn't start with the routing prefix <{{ routing4_prefix }}>
or is not in the routing6_cidr <{{ routing6_cidr }}>
3 changes: 3 additions & 0 deletions site.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
---
- name: Import verify_config playbook
ansible.builtin.import_playbook: verify_config.yml

- name: Prepare Proxmox cluster
hosts: proxmox
gather_facts: true
Expand Down
14 changes: 14 additions & 0 deletions verify_config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
- name: Gather config for verify
hosts: all
gather_facts: true
roles:
- role: verify_config_gather
when: verify_config is not defined or verify_config

- name: Verify config
hosts: localhost
gather_facts: false
roles:
- role: verify_config
when: verify_config is not defined or verify_config