Welcome to the ANTS Framework

ANTS Framework

ANTS is a framework to manage and apply macOS and Linux host configurations using Ansible Pull.

The ANTS Framework is developed by the Client Services Team of the University of Basel IT Services and released under the GNU General Public License Version 3.

Ansible is a trademark of Red Hat, Inc..

Introduction

The ANTS Framework consists of the following components:

  • A wrapper for Ansible-Pull
  • An Ansible Dynamic Inventory Script (MS Active Directory Connector)
  • A modular collection of roles ready to be used
  • Strong logging integration

Requirements

This project assumes that you are familiar with Ansible , Git and the shell.

Getting started

Installing ants using pip

  • Make sure Git is installed on your machine.
  • Install the latest ants client using pip: pip install ants_client.
  • Pip will install the ANTS client with a default configuration and put the executable in your path.

Installing ants using macOS .pkg installer

  • Download the latest .pkg installer from the releases page.
  • Execute the installer. This will take care of all dependencies.
  • A launch daemon will be installed, running ants every 15 minutes. It will trigger after the next restart.

Run ants

  • Open your terminal
  • Start an ANTS run by typing ants.
  • Wait for ANTS to finish, then open another shell. You will see the message of the day.

What happened?

Running ANTS with the default configuration will use ansible-pull to clone the ANTS playbook via https from a github repository and execute an ansible run.

By default, this will add a message of the day to your macOS or Linux host. Logs of all the runs are stored at /var/log/ants.

Also by default, ants will add github to your known_hosts file. This is important for later, when you want to enable git clone using ssh.

Where to go from here?

Look at the configuration

Besides the default configuration file in the ants_client package, ANTS will also look for a local configuration file at /etc/ants/ants.cfg.

You can use ants --show-config to list the current configuration and ants --initialize to generate a new configuration file at /ets/ants/ants.cfg.

Do not modify the default configuration file as it might be overwritten when updating ANTS.

Run other roles

Fork or duplicate our example playbook and change the client configuration to point to your repository. Update main.yml to assign different roles to your hosts.

You can use the default Ansible syntax. You can also use wildcards. Have a look at the Ansible documentation

Add ssh authentication to your repository

Ansible-pull can clone a git repository using ssh. You can enable this by creating your own private playbook, adding ssh authentication and a read only ssh key to the repository. Configure ANTS to use that key.

By default, ANTS will look for a private key at /etc/ants/id_ants

You can generate a key with ssh-keygen -t rsa -b 4096 -N '' -C "ants client" -f /etc/ants/id_ants

By default, ANTS is configured to run with strict host key checking disabled and will add the host key for your repo to your known_hosts file. You should change this in production. To do so, add ssh_stricthostkeychecking = True to your ants.cfg

Add a dynamic inventory source

Ansible supports dynamic inventory scripts. (A json representation of hosts to group mappings.)

You can use scripts to tell ansible-pull which tasks to run on which host. You need an inventory source and a script that can read and return it in the correct format:

By default, ANTS will run a dummy script inventory_default that will just return your hostname belonging to a group named ants-common.

But we also provide inventory_ad which will connect to your Active Directory and return all groups your host is a member of. Just add your configuration to /etc/ants/ants.cfg. Note that read only rights for the Active Directory user are sufficient.

By using a dynamic inventory source, you can assign roles to a host using AD and let ANTS handle the configuration.

Group Layout in Active Directory

The groups in Active Directory must have the same names as the mappings and the variables you want to assign using Ansible. We recommend to keep the groups in a dedicated Organizational Unit to prevent naming collisions.

Nested groups with access restrictions are an easy way to offer rights delegation to other units in your organization.

What else do I need

Nothing. You just set up a configuration management that communicates savely over ssh using your AD and Github.

No additional infrastructure required.

Communication

Comparison of plain Ansible and Ansible Tower to ANTS

What does ANTS do, that Ansible can not?

  • ANTS gives you a set of ready to be used roles for typical macOS and Linux host configurations.
  • ANTS let’s you utilize Active Directory to map computers to roles. With all it’s delegation and nesting features.
  • ANTS utilizes Ansible Pull and therefore does not require an active network connection to a central server. Roles will be locally applied even if the host is offline.

What does Ansible or Ansible Tower do that ANTS does not?

  • Tower has a nice Dashboard
  • Tower has a real time job output and push-button job runs
  • Tower can to job scheduling
  • Tower supports run-time job promoting
  • Tower supports workflows
  • Ansbile can use encrypted secrets using Vault
  • Ansible and Tower do offer Enterprise Support

Development Documentation

Please find the source code documentation below. To contribute please file a Pull Request on GitHub.

ants

Run ansible-pull with a set of defined parameters and log the content of these runs.

ants.find_file(f)

Search for a file in environment PATH and return first match or None

ants.parse_proc(proc)[source]

Read subprocess output and dispatch it to logger.

Cases handled separately:
  • task_line
    • Line with the name of a task.
    • Printed directly befor the task status.
  • recap_line
    • A single line a the end of an Ansible run.
    • It indicates the number of failes/changed/ok tasks.
    • The line after ‘PLAY RECAP’ contains the recap
ants.run_ansible(args)[source]

Run ansible-pull.

The ansible python api is provided as is and the core team reserve the right to push breakting changed.

Hence, we call the cli directly and do not attempt to work with the api.

Documentation: http://docs.ansible.com/ansible/dev_guide/developing_api.html

inventory_default

Default inventory script.

This is a dummy inventory script to bootstrap ants. It will return localhost and fqdn as members of the ants-common group.

These values can be used to assign roles in local.yml but using a dynamic inventory script like inventory_ad.py is the prefered way of running ants.

inventory_default.main()[source]

Print default inventory in JSON.

inventory_ad

This script is an Ansible Dynamic Inventory script. It takes the host name of a client, searches for it in MS Active Directory and returns it’s groups. The groups are filtered by prefix and Active Directory Organizational Unit.

This script returns the group and hostname in an Ansible compatible JSON format. The script writes an offline cache file for later use if the Active Directory query succeeds. This cache is returned if the Active Directory query fails.

It always returns the common group for common tasks. It also returns the group common-ad-bound if the result comes from an online query rather than from the cache.

inventory_ad.connect_to_ad(ldap_user, ldap_pw, ldap_host)[source]

Connect to Active Directory and return the connection or None.

inventory_ad.format_output(output)[source]

Return results in Ansible JSON syntax.

Ansible requirements are documented here: http://docs.ansible.com/ansible/latest/dev_guide/developing_inventory.html

inventory_ad.get_computer_dn(connection, simple_hostname, ldap_ou)[source]

Take the simple host name and return it’s distinguished name

inventory_ad.get_computer_groups(connection, search_base, computer_dn, group_prefix)[source]

Receive groups that the computer object is a member off. member:1.2.840.113556.1.4.1941:=%s is a special Active Directory OID that returns nested groups and not just the first level. The result is filtered by group prefix and Organizational Unit

inventory_ad.get_simple_host_name(fqdn)[source]

Convert FQDN to simple host name and return it.

inventory_ad.host_exist_in_ad(connection, simple_hostname, ldap_ou)[source]

Check if host can be found in Active Directory. The host does not have to be bound to AD it just has to exist.

inventory_ad.main()[source]

Fetching groups from AD and printing them in JSON.

inventory_ad.read_cache(cache_file)[source]

Read cache file and return content.

inventory_ad.write_cache(cache_file, output)[source]

Write inventory cache to file.

configer

Handle parsing of configuraiton file options.

configer.create_dir(dir_name, user='root', group='wheel')[source]

Create directory

Use pwd and grp to get uid/gid. https://stackoverflow.com/questions/5994840/how-to-change-the-user-and-group-permissions-for-a-directory-by-name

configer.get_config()[source]

Get configuration from command line and return ConfigParser opject

If no value is specified by the user, the value marked as example will be written set.

Only values that differ from the system defaults are written to the local config file.

configer.get_values(cfg, section_name)[source]

Take and return a dict of values and prompt the user for a reply.

configer.is_root()[source]

Check if user is root and return True or False.

configer.read_config(config_section, config_file='ants.cfg')[source]

Read indicated configuraton section and return a dict.

Uses config.optionxform to preserver upper/lower case letters in config file.

configer.write_config(config, config_file='ants.cfg')[source]

Writing ConfigParser object to local configuration. Existing files will be overwritten.

logger

Handle logging.

logger.get_logger(name, logfile=False, maxBytes=0, formatter='default')[source]

Return logging object with handler and formatter.

logger.log_recap(start_time, end_time, status_line)[source]

Log play recap in a dedicated form.

Rollover old logfiles befor writing.

logger.parse_client_status(status_line)[source]

Read client status from recap log.

Status can have one of the following forms: * ok * changed * failed

We expect IndexError because play recap will be empty if no matching hosts were found. Client status will be set to failed in that case.

Status will also be set to failed of no log file could be found.

logger.status_file_rollover()[source]

Rotate log files at the start of every ansible run

Keep track of ok/changed/failed on a per run basis. Hence, rotate them at the start of each run.

Code for rotation based on https://stackoverflow.com/questions/4654915/rotate-logfiles-each-time-the-application-is-started-python

logger.write_log(line, task_line=None, debug=False)[source]

Write log to stdout and log files.

Highlight ansible run status in stdout.

argparser

Custom argparse actions and main argparse are defined here.

class argparser.GetActiveBranchAction(option_strings, branch, dest, nargs=None, **kwargs)[source]

Print active git brunch to stdout and exit.

class argparser.GetGitRepoAction(option_strings, repo, dest, nargs=None, **kwargs)[source]

Print active git repo to stdout and exit.

class argparser.GetGroupsAction(option_strings, inventory_script, dest, nargs=None, **kwargs)[source]

Execute inventory script and exit.

class argparser.GetPolicyVersionAction(option_strings, uccm_dir, dest, nargs=None, **kwargs)[source]

Print repo version file content and exit.

class argparser.GetStatusAction(option_strings, logfile, dest, nargs=None, **kwargs)[source]

Print ants status to stdout and exit.

class argparser.InitializeAntsAction(option_strings, dest, nargs=None, **kwargs)[source]

Prompt user for configuration input and write local config file.

class argparser.ShowConfigAction(option_strings, dest, nargs=None, **kwargs)

Print ants configuration to stdout and exit.

argparser.parse_args(version, LOG_RECAP, DESTINATION, CFG)[source]

Parse and return command line parameters.

This function relies on the custom actions defined previousely. Custom actions will be called if the corresponding argument is entered on the command line.

argparser.str2bool(v)[source]

Make argparse read strings and return bool values.

Source: https://stackoverflow.com/questions/15008758/parsing-boolean-values-with-argparse

Indices and tables