From 69cac4d4be4114a7a61350c84d7e7c65b83f62c8 Mon Sep 17 00:00:00 2001 From: GROUPE 6 Date: Fri, 12 May 2023 19:25:57 +0200 Subject: [PATCH] feat(role: common): add common roles --- group_vars/all/main.yml | 5 + group_vars/all/vault.yml | 19 ++ hosts.yml | 21 ++ playbooks/base.yml | 14 ++ playbooks/full.yml | 7 + requirements.yml | 9 + roles/common/main.yml | 13 ++ roles/common/tasks/packages.yml | 30 +++ roles/common/tasks/password.yml | 6 + roles/common/tasks/timezone.yml | 5 + roles/common/tasks/user.yml | 16 ++ simple-ansible-inventory.py | 327 ++++++++++++++++++++++++++++++++ 12 files changed, 472 insertions(+) create mode 100644 group_vars/all/main.yml create mode 100644 group_vars/all/vault.yml create mode 100644 hosts.yml create mode 100644 playbooks/base.yml create mode 100644 playbooks/full.yml create mode 100644 requirements.yml create mode 100644 roles/common/main.yml create mode 100644 roles/common/tasks/packages.yml create mode 100644 roles/common/tasks/password.yml create mode 100644 roles/common/tasks/timezone.yml create mode 100644 roles/common/tasks/user.yml create mode 100755 simple-ansible-inventory.py diff --git a/group_vars/all/main.yml b/group_vars/all/main.yml new file mode 100644 index 0000000..d83f1bc --- /dev/null +++ b/group_vars/all/main.yml @@ -0,0 +1,5 @@ +--- + +root_password_hashed: "{{ vault_root_password_hashed }}" +user_password_hashed: "{{ vault_root_user_hashed }}" +user_ssh_key: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDl05rLhOKK4M2pqp7xRbKzIYlnkLRvp61NLrP2E0fiU l01@L01" diff --git a/group_vars/all/vault.yml b/group_vars/all/vault.yml new file mode 100644 index 0000000..158b4a0 --- /dev/null +++ b/group_vars/all/vault.yml @@ -0,0 +1,19 @@ +$ANSIBLE_VAULT;1.1;AES256 +36346637303464633032623363643762663630363863323565623263343931393834306138666463 +3934336362316235323039616435653764323936613338340a616434656434303138646637663962 +34363762333634393863653634316638303865373632396231623734303239356365626661363832 +3039613031346637630a626464396530326237326338376166393663356538313731653639373661 +38373061313337323938656165343965633732626335653739656464343431343364326362333038 +39323834633434343062303962366531643734363235326564303538613161373161383364343539 +64646336316538613535613464623631653730316365323539396533343731356263323632383233 +62393262653637616239643834316166316432383230373232386131313866326237663265383130 +61623736393261656437346236666664393365666637366531636563303933663832396163326366 +39656164396462666634303732396439636462626366313663663766303632353266633139343939 +61336236366334336536626161353330646533663265353161643538336434623834663064323565 +33376534323330616238376562623763346565303237366639663133656562623762303961333062 +30626630383232656636363131343135626432613638623664336232376266623936633436613735 +31373033616163313239656465356632343536356637623336393965376565356338323365323862 +35653362376537396636303337306663306235653661353831616337346562643963643935653735 +63663263326466626365393634373133313239303337633766386238613634633337666536663332 +38326438363361323830356632363863636332333039353865363032613133613062323763303565 +3630663937633964666135323666326530633266353232346337 diff --git a/hosts.yml b/hosts.yml new file mode 100644 index 0000000..da0331a --- /dev/null +++ b/hosts.yml @@ -0,0 +1,21 @@ +--- +#### YAML inventory file + +# the two first lines above are needed for the script +# to identify the file as an inventory file + +## Structure +# hosts: +# - host: +# # if needed +# hostvars: +# key: value +# # if needed +# groups: +# - group1 +# - group2 + +hosts: + - host: 192.168.3.2 + groups: + - nginx diff --git a/playbooks/base.yml b/playbooks/base.yml new file mode 100644 index 0000000..2990e9a --- /dev/null +++ b/playbooks/base.yml @@ -0,0 +1,14 @@ +--- + +- name: Gather facts + hosts: all + gather_facts: true + tags: + - always + +- name: Common + hosts: all + roles: + - common + tags: + - common diff --git a/playbooks/full.yml b/playbooks/full.yml new file mode 100644 index 0000000..c190621 --- /dev/null +++ b/playbooks/full.yml @@ -0,0 +1,7 @@ +--- + +- name: Base + import_playbooks: base.yml + + #- name: Nginx + # import_playbooks: nginx.yml diff --git a/requirements.yml b/requirements.yml new file mode 100644 index 0000000..29512cb --- /dev/null +++ b/requirements.yml @@ -0,0 +1,9 @@ +--- + +collections: + - name: ansible.utils + version: 2.9.0 + - name: community.crypto + version: 2.10.0 + - name: community.general + version: 3.4.0 diff --git a/roles/common/main.yml b/roles/common/main.yml new file mode 100644 index 0000000..82c6160 --- /dev/null +++ b/roles/common/main.yml @@ -0,0 +1,13 @@ +--- + +- name: Packages + ansible.builtin.import_tasks: packages.yml + +- name: Service account + ansible.builtin.import_tasks: user.yml + +- name: Root password + ansible.builtin.import_tasks: password.yml + +- name: Timezone + ansible.builtin.import_tasks: timezone.yml diff --git a/roles/common/tasks/packages.yml b/roles/common/tasks/packages.yml new file mode 100644 index 0000000..d418862 --- /dev/null +++ b/roles/common/tasks/packages.yml @@ -0,0 +1,30 @@ +--- + +- name: Install aptitude + ansible.builtin.apt: + name: aptitude + update_cache: true + force_apt_get: true + +- name: Install common packages + ansible.builtin.apt: + install_recommends: false + update_cache: true + state: present + name: + - bash + - curl + - file + - git + - htop + - iftop + - iotop + - ldnsutils + - lsof + - man + - molly-guard + - rsync + - sudo + - tcpdump + - tmux + - traceroute diff --git a/roles/common/tasks/password.yml b/roles/common/tasks/password.yml new file mode 100644 index 0000000..c53bb62 --- /dev/null +++ b/roles/common/tasks/password.yml @@ -0,0 +1,6 @@ +--- + +- name: Set root password + ansible.builtin.user: + name: root + password: "{{ root_password_hashed }}" diff --git a/roles/common/tasks/timezone.yml b/roles/common/tasks/timezone.yml new file mode 100644 index 0000000..3fa2d16 --- /dev/null +++ b/roles/common/tasks/timezone.yml @@ -0,0 +1,5 @@ +--- + +- name: Set timezone to Europe/Paris + community.general.timezone: + name: Europe/Paris diff --git a/roles/common/tasks/user.yml b/roles/common/tasks/user.yml new file mode 100644 index 0000000..3ff38db --- /dev/null +++ b/roles/common/tasks/user.yml @@ -0,0 +1,16 @@ +--- + +- name: Add service account ansible + ansible.builtin.user: + name: ansible + state: present + shell: /bin/bash + groups: sudo + append: yes + password: "{{ user_password_hashed }}" + +- name: Add ssh key to the user ansible + authorized_key: + user: ansible + state: present + key: "{{ ssh_key }}" diff --git a/simple-ansible-inventory.py b/simple-ansible-inventory.py new file mode 100755 index 0000000..f9f1ae2 --- /dev/null +++ b/simple-ansible-inventory.py @@ -0,0 +1,327 @@ +#!/usr/bin/env python + +import logging +import os +import argparse +import yaml +import json +import re +import copy +import textwrap + +""" +Project repo +https://github.com/leboncoin/simple-ansible-inventory + +For further details about Ansible best practices including directory layout, see +https://docs.ansible.com/ansible/2.5/user_guide/playbooks_best_practices.html + +For further details about developing Ansible inventory, see +http://docs.ansible.com/ansible/latest/dev_guide/developing_inventory.html +""" + +INVENTORY_SCRIPT_NAME = "SimpleAnsibleInventory" +INVENTORY_SCRIPT_VERSION = 1.0 +LOGGER = None +INVENTORY_FILE_REGEX_PATTERN = ".*\.y[a]?ml" +INVENTORY_FILE_HEADER_SIZE = 28 +INVENTORY_FILE_HEADER = "---\n#### YAML inventory file" +INVENTORY_FILE_ENV_VAR = "ANSIBLE_YAML_INVENTORY" +ACCEPTED_REGEX = r"\[(?:(?:[\d]+-[\d]+|[\d]+)+,?)+\]" + + +def build_meta_header(host, meta_header): + """ + Progressively build the meta header host by host + + :param host: current host to add to meta header + :type host: dict + :param meta_header: meta header to build + :type meta_header: dict + :return: + """ + # If found host doesn't exists in dict, we create it + if host['host'] not in meta_header['hostvars']: + meta_header['hostvars'][host['host']] = dict() + # Browsing and adding all vars found for host + if 'hostvars' in host: + for hostvar in host['hostvars']: + meta_header['hostvars'][host['host']][hostvar] = \ + host['hostvars'][hostvar] + # Return new meta_header version containing new host + return meta_header + + +def build_groups(host, partial_inventory): + """ + Progressively build groups conf host by host + + :param host: current host to add to meta header + :type host: dict + :param partial_inventory: Only contains _meta header + :type partial_inventory: dict + :return: filled inventory + """ + # check if 'all' group exists, if no, create it + if 'all' not in partial_inventory: + partial_inventory['all'] = dict() + partial_inventory['all']['hosts'] = list() + partial_inventory['all']['vars'] = dict() + partial_inventory['all']['children'] = list() + # If groups section doesn't exists return inventory without modification + if 'groups' not in host: + return partial_inventory + # For each group of the host + for group in host['groups']: + # If groups doesn't already exists, creating it + if group not in partial_inventory: + partial_inventory[group] = dict() + partial_inventory[group]['hosts'] = list() + partial_inventory[group]['vars'] = dict() + partial_inventory[group]['children'] = list() + # add group to 'all' group if not already in + if group not in partial_inventory['all']['children']: + partial_inventory['all']['children'].append(group) + partial_inventory[group]['hosts'].append(host['host']) + return partial_inventory + + +def get_int_interval(from_int, to_int): + """ + Return a list of all integers between two integers + + :param from_int: start from + :type from_int: int + :param to_int: end at + :type to_int: int + :return: list(int) + """ + LOGGER.debug("Calculating int interval between " + str(from_int) + + " and " + str(to_int)) + return [str(value) for value in range(from_int, to_int + 1)] + + +def all_string_from_pattern(input_string, matching_part): + """ + Return a list of all string matching the input string containing a pattern + + :param input_string: input string containing pattern + :type input_string: str + :param matching_part: pattern extracted from hostname + :type matching_part: str + :return: str + """ + # Transform matched pattern to a list of ranges + regex_found = matching_part.group(0).replace("[", "").replace("]", "").split(',') + possibilities = list() + # let's fill all ranges + for pattern in regex_found: + split_range = pattern.split('-') + int_1 = int(split_range[0]) + int_possibilities = [int_1] + if len(split_range) == 2: + int_1 = min(int_1, int(split_range[1])) + int_2 = max(int(split_range[0]), int(split_range[1])) + int_possibilities = get_int_interval(int_1, int_2) + LOGGER.debug("Possibilities: " + str(int_possibilities)) + for possibility in int_possibilities: + possibilities.append( + input_string[:matching_part.start(0)] + + str(possibility) + + input_string[matching_part.end(0):] + ) + return possibilities + + +def patterning_hosts(regex_found, host, filled_pattern_host_list): + """ + Function used recursively to fill all patterns in hostname + + :param regex_found: re.match object + :type regex_found: re.match() + :param host: host read in conf + :type host: dict + :param filled_pattern_host_list: list containing all hosts + with all patterns filled + :type filled_pattern_host_list: list + :return: + """ + LOGGER.debug("Processing regex " + str(regex_found.group(0)) + + " found in host name: " + host['host']) + # For each hostname possibility with first pattern + for patterned_host in all_string_from_pattern(host['host'], regex_found): + # Checking if there is still another pattern left in hostname + regex_found = re.search(ACCEPTED_REGEX, patterned_host) + # build a new host with the hostname + new_host = dict(host) + new_host['host'] = patterned_host + # If hostname still containing pattern, call itself + if regex_found: + patterning_hosts(regex_found, new_host, filled_pattern_host_list) + # If no pattern left, append host to list + else: + filled_pattern_host_list.append(new_host) + + +def get_inventory_recursively(raw_conf): + """ + Build and return the inventory + + :param raw_conf: Raw configuration loaded from yml configuration file + :type raw_conf: dict + :return: dict + """ + LOGGER.debug("Building full inventory from loaded YAML(s)") + inventory = dict() + meta_header = dict() + meta_header['hostvars'] = dict() + # Browsing all hosts + for host in raw_conf['hosts']: + LOGGER.debug("Processing host entry " + str(host)) + filled_pattern_host_list = list() + regex_found = re.search(ACCEPTED_REGEX, host['host']) + # If no regex pattern, directly add the host + if not regex_found: + filled_pattern_host_list.append(host) + # Else fill all patterns + else: + patterning_hosts(regex_found, host, filled_pattern_host_list) + LOGGER.debug("Host(s) generated from this host entry: " + + str([hn['host'] for hn in filled_pattern_host_list])) + for filled_pattern_host in filled_pattern_host_list: + # Complete meta header for each host + meta_header = build_meta_header(filled_pattern_host, meta_header) + inventory = build_groups(filled_pattern_host, inventory) + inventory['_meta'] = meta_header + return inventory + + +def find_inventory_files(): + """ + find the inventory file in sub folders + + :return: string + """ + if INVENTORY_FILE_ENV_VAR in os.environ: + LOGGER.debug("env VAR " + INVENTORY_FILE_ENV_VAR + " found") + return [os.environ[INVENTORY_FILE_ENV_VAR]] + inventory_files = list() + LOGGER.debug("Looking for inventory files") + # script py path + script_path = os.path.realpath(__file__) + inventories_path = os.path.dirname(script_path) + # walking through script folder looking for yaml files + for root, dirnames, filenames in os.walk(inventories_path): + LOGGER.debug("All files found: " + str(filenames)) + for file in [f for f in filenames if re.search(INVENTORY_FILE_REGEX_PATTERN, f)]: + # if file beginning match header + with open(os.path.join(root, file), 'r') as fd: + if fd.read(INVENTORY_FILE_HEADER_SIZE) == INVENTORY_FILE_HEADER: + inventory_files.append(os.path.join(root, file)) + return inventory_files + + +def list_all_hosts(): + """ + Build the dictionary containing all hosts + + :return: dict + """ + LOGGER.debug("listing all hosts") + raw_confs_list = list() + # Load all configuration files + inventory_files = find_inventory_files() + LOGGER.debug("Inventory files found: " + str(inventory_files)) + # If no inventory files found, return empty inventory + if not len(inventory_files): + return {"_meta": {"hostvars": {}}, "all": {"children": ["ungrouped"]}} + for inventory_file in inventory_files: + with open(inventory_file, 'r') as fd: + LOGGER.debug("Loading file: " + inventory_file) + raw_confs_list.append(yaml.safe_load(fd)) + # Copy first conf loaded to another object + raw_conf = copy.deepcopy(raw_confs_list[0]) + # Delete first conf loaded + raw_confs_list.pop(0) + # Append all others conf to the first one by merging dictionaries + LOGGER.debug("Merging files if needed") + for conf in raw_confs_list: + for key, value in conf.items(): + raw_conf.setdefault(key, []).extend(value) + inventory = get_inventory_recursively(raw_conf) + LOGGER.debug("Inventory found: " + str(inventory)) + return inventory + + +def create_logger(): + """ + Create a logger instance + + :return: logger instance + """ + logger = logging.getLogger() + handler = logging.StreamHandler() + formatter = logging.Formatter('%(asctime)s - %(message)s') + handler.setFormatter(formatter) + logger.addHandler(handler) + logger.setLevel(logging.INFO) + return logger + + +def parse_arguments(): + """ + Initialize the parser, flags list is mandatory + + :return: parsed arguments + """ + epilog = ''' + By default the script will walk in script folder and in all its subfolders + looking for inventory files. + If a filename match the regex + %s + and if the first %d + %s + the file will be considered as an inventory file + + If the environment variable INVENTORY_FILE_ENV_VAR is found, the only + inventory file read will be the file specified in the environment + variable. + ''' % (str(INVENTORY_FILE_REGEX_PATTERN), + INVENTORY_FILE_HEADER_SIZE, + INVENTORY_FILE_HEADER.replace('\n', '\n\t')) + parser = argparse.ArgumentParser( + formatter_class=argparse.RawDescriptionHelpFormatter, + description="YAML Ansible inventory script loader", + epilog=textwrap.dedent(epilog) + ) + parser.add_argument('--list', + action='store_true', + help="display all loaded inventory") + parser.add_argument('--host', + nargs=1, + help="display vars for specified host") + parser.add_argument('-v', '--verbose', + action='store_true', + help="enable verbose mode") + parser.add_argument('-V', '--version', + action='store_true', + help="display inventory script version and exit") + return parser.parse_args() + + +if __name__ == "__main__": + LOGGER = create_logger() + parsed_arguments = parse_arguments() + if parsed_arguments.verbose: + LOGGER.setLevel(logging.DEBUG) + for hdlr in LOGGER.handlers: + hdlr.setLevel(logging.DEBUG) + if parsed_arguments.version: + LOGGER.debug("version flag found") + print(INVENTORY_SCRIPT_NAME + " v" + str(INVENTORY_SCRIPT_VERSION)) + elif parsed_arguments.list: + LOGGER.debug("list flag found") + print(json.dumps(list_all_hosts())) + elif parsed_arguments.host: + LOGGER.debug("host flag found") + print(json.dumps(dict()))