Friday, 14 January 2022

Ansible Fundementals - With Image Partial

Creating a Static Inventory of Managed Hosts

Uses INI style and can also be defined in YAML. Inventory can also be populated dynamically

 

#will supply the path to the Ansible configuration file that's currently in use

ansible --version

 

#We can match the entire CIDR notation of 192.168.4.0/22 using below

192.168.[4:7].[0:255]

 

#To match server01 through server20. Notice we have used 01 instead of 1, so this will match 01, 02, 03 ... instead of 1, 2, 3

server[01:20]

 

#To convert inventory to YAML

ansible-inventory -y --list

 

#To check if the host is defined in inventory:

ansible washington1.example.com --list-hosts

 

#Default ansible config file

/etc/ansible/ansible.cfg

 

#Default inventory file location:

/etc/ansible/hosts

 

#Creating our own inventory file:

[webservers]

web01

web02

 

#Once inventory is added list it using below command

#Since we need output from our own file we use -i and point it to our file "inventory"

#The output will be in json format

ansible-inventory -i inventory --list

 

 

Managing Connection Settings and Privilege Escalation

In this section, we'll explore managing the connection settings and privilege escalation.

 

Agentless architecture.

Linux - SSH and Python

Windows - Windows RM and PowerShell

 

Inventory will describe the list of hosts that we wish for Ansible to manage, we now can go forward and understand how to describe to Ansible the various bits of information.

 

Ansible configuration file:

If you're unsure which configuration file you're using, you can use the ‑‑version flag for the Ansible command.

ansible --version

 

By default, Linuxbased systems will take advantage of the SSH protocol. If you wish to define a separate protocol or a nonstandard port, you'll need to describe that to Ansible as well.

 

The user being used to log into the system can also be described in inventory.

 

Once you've gained access to a system, if you need to escalate privileges to the administrative or root credentials, Ansible will need to understand how that occurs in your environment. By default, it will use the sudo command to do so. Other options exist, and if you do use a different privilege escalation method, such as the su command.

 

Lastly, you can describe to Ansible whether an SSH password should be provided or if keybased authentication is in place.

 

All of these defaults can be adjusted within the Ansible configuration file or by passing a set of flags on the command line during invocation.

 

 

which Config File Ansible looks for in which order?

It is not uncommon to have several configuration files for different Ansible workloads in your environment.

 

1.Ansible does consult an ANSIBLE_CONFIG environmental variable. If this is set, it will be set to the path of an Ansible configuration file.

2.If environmnet variable is not set, Ansible looks for the configuration file in current working directory.

3.If it doesn't find an ansible.cfg file in the current working directory, it will then look in your home directory for a dot file, or hidden file. .ansible.cfg

4.Lastly, if it hasn't found a configuration file in any of those locations, it will use the default installation file at /etc/ansible/ansible.cfg.

 

Again, just a reminder that the flag ‑‑version for the Ansible command will clearly spell out which configuration file is being consulted. Be sure if you navigate around the file system you can check this because you may have switched directories to one that contains an alternative ansible.cfg file.

 

ansible.cfg:

The ansible.cfg file consists of several sections. Each section contains a heading and has a collection of key value pairs. The section headings, or titles, are enclosed within square brackets, and then the key value pairs are set as key equals value. The basic operations of Ansible executions take advantage of two main sections. One is the default section for Ansible operations, and the second is the privilege_escalation section where Ansible looks to understand how to gain privilege escalation when invoked for your managed hosts. The connection settings we discussed previously will be defined within the default section of the configuration file. This will include three main pieces of information for Ansible to understand.

 

default section: [defaults]

1.Remote_user will explain which user to take advantage of when connecting to managed hosts. If you do not supply a remote user argument, it will use your current username.

2.Remote_port specifies which SSH port you'll use to contact your managed host. By default, this is port 22.

3.The ask_pass argument controls how or whether or not Ansible will prompt you for an SSH password. By default, it does not prompt for a password, as it is most customary and a best practice to use keybased authentication for SSH connections.

 

privileg escalation section: [privileg escalation]

In the privilege_escalation setting section of the configuration file, several main arguments are used for Ansible to understand how to escalate privileges to a highertiered user, such as the root user.

 

The become key will describe whether or not you will automatically use privilege_escalation. This is a Boolean, and the default is set to no.

The become_user key will define which user to switch to when privilege escalation occurs. By default, this is the root user. The become_method key will determine how Ansible will switch to becoming that escalated user. Sudo is the default implementation; however, there are other options, such as su.

The become_ask_pass key will control whether or not Ansible prompts you for a password when escalating privileges. By default, this is set to no.

 

Example of an ansible.cfg file:

In general, an ansible.cfg file should contain only the keys you're overriding from defaults.

[defaults]

inventory = ./inventory

remote_user = ansible

ask_pass = flase

 

[privilege_escalation]

become = true

become_user = root

become_ask_pass = false

 

In a typical environment, not all hosts are equal, and there could be different properties we wish to set as variables on specific hosts.

 

One of the easiest ways to provide hostspecific variables is to create a host_vars directory. In that directory you'll create a text file that matches the hostname. Within this text file, you can supply a list of key value pairs that are unique to that host. Any variables provided in this fashion will override the one set within the ansible.cfg file. There's also a slight different syntax and naming when it comes to using this method.

 

Hostbased connection an privilege escalation variables:

Ansible_host will specify a different IP or hostname to use when connecting to the host instead of the one that specified an inventory. Think of this as a secondary IP or alternative hostname for that host. Ansible_port will specify the SSH port that you would prefer to use for connecting to that host.

Ansible_user specifies the user for that connection.

Ansible_become will specify whether or not you should use privilege escalation for that host.

ansible_become_user specifies which user to become on that host.

ansible_become_method specifies the methodology on how privilege escalation works, whether this be sudo, su, or something alternative.

 

Example of some hostbased connection variables in a host_vars subdirectory:

Here we have a subdirectory, host_vars, containing the file server1.example.com. The server1.example.com file contains variables specifically used when connecting and manipulating server1.example.com only.

 

File -> project/host_vars/server1.example.com

 

ansible_host: 192.0.2.104

ansible_port: 34102

ansible_user: root

ansible_become: false

 

No other servers will inherit this, but this will override any defaults that are contained in ansible.cfg when you interact with server1.example.com.

 

Creating ansible.cfg:

When creating this file, refer the default file in /etc/ansible/ansible.cfg. They have already listed the defaults, just take them and copy to your file and add setting only if there is change.

 

To filter the inventory:

ansible databases --limit db01 -m pint

 databases -> Heading in inventory

 

 

Running Ad Hoc Commands

Ansible provides a catalogue of modules. These are the underlying code that explains via code how Ansible can provide the automation tasks we'll leverage. Modules exist for a large number of system administrative tasks such as creating and managing users, installing and removing or even updating software, deploying configurations, as well as configuring the network services that run on your systems. Ansible modules are what is known as idempotent. In other words, they'll always check to see if the work being requested is required on the system or if it's already in the desired state. If a system is already in the desired state described by your Ansible work, then it will skip that and report back that no change was necessary. If a change is required, Ansible will then perform that change and report that as well.

 

An ad hoc command runs a single module against a specified host to perform a single task. To run ad hoc commands, we'll use the ansible command.

After the ansible command, you'll need to supply a hostpattern. This hostpattern will specify which host this task will run on.

Additionally, you'll need to specify a module using the m flag. Each module takes a unique set of arguments, which you'll provide with the a flag.

Lastly, you'll specify an inventory file with the i flag, where the host can be found for Ansible.

 

One of the simplest ad hoc commands, as well as one of the most common system administrative tasks, is ping. The ping module doesn't actually send an ICMP packet like we're used to as system administrators using the ping command, but it does check to see if Ansible can contact the managed host. Specifically in a Linuxdefault implementation, this would be using an SSH interaction.

 

ansible all -m ping

 

When using Ansible ad hoc commands, there're a number of flags available to the user to override default behaviors. The default behaviors are defined in the ansible.cfg configuration file.

We can see that it may be necessary to tell Ansible that we need a prompt for a password for our ad hoc command. You can use the k flag or the ‑‑askpass flag for this behavior.

When you need to specify a specific user for the interaction, the u flag will allow you to do so. This will be overriding the REMOTE_USER setting contained within ansible.cfg.

A b enables privilege escalation akin to the become argument within our configuration file.

The capital K flag denotes that we need to be prompted for a password during privilege escalation, and the flag ‑‑becomemethod overrides the default privilege escalation method.

 

With Ansible, the default is sudo. Other valid choices exist such as su and can be seen using the ansibledoc command. Most Ansible modules take a set of arguments to describe the actions you wish for Ansible to perform.

The a flag in an ad hoc command allows you to supply those arguments. Syntactally, we'll contain those within single quotes and put a space between each keyvalue pair. Example,

 

m flag to declare the module user

a flag to specify those arguments.

 

Also important to consider is the concept of state. Here we've declared a state=present. We'll take a look through this course at state as that is a main approach of how Ansible has you describe the behavior you wish for it to perform.

 

ansible -m user -a 'name=newbie uid=4000 state=present' servera.lab.example.com

 

For example, if we wish to remove this user, we could change the state to absent and rerun this command. That would then remove the user we had just created.

 

Below is the helpful list showing some of the flags you have available to you during ad hoc commands.

 

Configuration Directive

Command-Line Option

inventory

-i

remote_user

-u

become

-b

become-method

--become-method

become_user

--become-user

become_ask_pass

-K

 

To take a look at the list of modules available to us for ad hoc or oneoff commands, we can use the ansibledoc -l command.

With the l flag, it will list all modules available to us. Use grep to filter

 

ansible-doc ping

 

I'll show a new technique of limiting to a single host. I'll then use an explicit statement for our inventory, the k flag to tell Ansible that I wish for it to prompt me for passwords when authenticating to this host, and then a simple module call to ping. We're prompted for that password, and supplying our user's password for that system, the command continues. Note that instead of all systems responding here, only the limited web01 system responds.

 

ansible all --limit web01 -i inventory -k

 

 

Let's try an ad hoc command to restart sshd on one of our targeted hosts. I'll use an ansible command. This time I'll allow it to rely on the inventory we know it'll be using instead of explicitly stating it to show you how simple ad hoc commands can be. I'll target all hosts. I'll call the module service. And I'll supply the arguments for that module. We'll use the key state setting it to the value restarted. Additionally, we'll name the service we wish to restart.

 

ansible all -m service -a "state=restarted name=sshd"

 

Let's try one more example of ad hoc commands. Let's try to create users on our target machines. First, let's take a look at the Ansible module for user. We can note that the equal sign denotes mandatory fields, and not many of them are mandatory here, but we do have a lot of flexibility with the user module itself. I'll create a simple user by name. Here we can see that name is one of the mandatory fields, so I'll create a simple user using name and set a simple password. Let's use the user module to create a simple user across our web server systems.

 

ansible webservers -m user -a "name=test password=secret state=present"

 

Creating a Simple Playbook

Playbook:

Playbook contains one or more plays, a play is an ordered list of tasks to run against hosts in your inventory. Each task will take advantage of a specific Ansible module to perform some action against your managed hosts. Most of the tasks authored throughout the modules are idempotent and can safely be executed over and over again without issue. The intention of a playbook is to alter lengthy, complex manual system administration into easily repeatable routines. This should provide predictability, as well as reusability for the work you author with Ansible.

 

Create simple playbooks

First thing to know when formatting an Ansible playbook is about YAML. YAML is a simple to author structure with standard file extensions ending in .yml. Twospace indentation with the space character only is the main concept behind the syntax within YAML files. Note that the spaces cannot be substituted with the tab character. The tab character is not allowed in proper YAML. YAML doesn't place strict requirements on how many spaces are used for the indentation, but the two basic rules come into play

Data elements at the same level in the hierarchy must align with the same indentation.

Items that are children of another item must be indented more than their parents. All children of the same data element, again, must be indented with the same indentation.

Proper playbooks always begin with three dashes to denote the start of a file. These will be all the way left justified.

Failures will result in immediately halting the play execution.

Within our inventories, we may not always find it appropriate to target every node within a group or even the entire inventory. This is where the ‑‑limit flag will allow us to target specific hosts within our inventory. The limit is a host pattern that further limits the hosts for the play. Given our playbook targeting all hosts, we could then supply a ‑‑limit argument and call out a singular host, or even a host pattern for this to execute upon.

ansible-playbook site.yml --limit datacenter2

The Ansible playbook command also provides us a helpful syntaxcheck argument. We can call ansibleplaybook, passing in the argument for ‑‑syntaxcheck. Then, just simply name your YAML file you wish for the syntax check to be performed upon. If any errors are found, Ansible will do its best to denote where in the file that error exists.

ansible-playbook –syntax-check webserver.yml

It can often be advantageous before performing the actual execution of a playbook to do a test or dry run of its work. The Ansible playbook argument provides the C argument to be able to do just that. You can see an example here of ansibleplaybook using the C argument on the webserver.yml playbook file. The resulting output simulates what would occur if you remove that flag, but does not actually perform that work. Once the work has been validated and you approve for this to carry on, simply remove the flag and run this again.

Gathering Facts is a builtin feature of Ansible executions where Ansible will profile all the targeted hosts to understand as much as it can about them.

 

Using Variables in Plays

Naming our variables:

There are a few rules. Variable names always start with a letter. Additionally, they can only contain letters, numbers, and underscores. Periods and dashes are not allowed in variable names.

Scope of variable

Once you've begun to create variables, it's important to understand the scope or the available reach for each variable you've created. The concept of global, host, and playbased scopes exists.

Global variable -> One that is set for every host. An example of this would be extra variables we create within a job template.

Hostbased values -> Are set for a particular host or host group. These would include variables we set in the inventory or in our host_vars directory as explored in a previous module.

Playscoped variables -> Are available for all hosts in the context of a currently executing play. These playbased scoped variables include things included in the vars directive at the top of a play or in the include_vars tasks contained.

 

Variable Precedence:

When variables are defined in multiple places, precedence also has to be considered. If a variable is defined at multiple levels, the level with the highest precedence will take over. A narrowscoped variable, in general, will take precedence over a widerscoped variable. Considering the types we discussed in our previous slide, this would mean that a playscoped variable would override a globalscoped variable. Variables defined within a playbook are overridden by extra variables defined on the command line during execution. To override in this manner, simply provide the e option and the substituted value for any variables you wish to override when you're calling ansibleplaybook.

Defining Variable:

A common method is to place a vars block at the beginning of the play and then list the variables you wish to define. You can see an example of vars being defined in this way in this block where the user_name and user_state are defined in a vars block at the top of a play.

- hosts: all

  vars:

    user_name: joe

    user_state: present

You could additionally define these variables in an external file. If you do so in this manner, we use the vars_files argument at the top of a play to load variables in from a file located elsewhere. You can see an example here where the vars_files block is created and the relative path to the vars directory and a users.yml file has been provided.

- hosts: all

  vars_files:

    - vars/users.yml   

 

Referencing Variable:

Once defined, variables can then be used within your tasks contained in a playbook. When we're ready to reference a variable within a play execution, we'll substitute its value by using double braces {{ variable_name }}. The double braces will contain the name of the variable we wish to substitute in. You can see an example here where we have a variable defined in the vars block at the top of the play as use_name. The value this is set to is joe. Within our task, we're creating the user Joe by using variable interpolation. You can see the double brace nomenclature utilized to substitute the value joe for the variable user_name. We're doing so in two places, both in the name of the task, as well as in the name provided for the argument for the user module.

- name: Example play

  hosts: all

  vars:

    user_name: joe

 

  tasks:

    - name: Create user {{ user_name }}

      user:

        name:"{{ user_name }}"

        state: present

When referencing one variable as another variable's value, the double brace will start the value. When it does, you may also need to quote around this value. This will prevent Ansible from interpreting the variable reference as starting a YAML dictionary. Ansible provides the helpful hint that the with_items without the quotation marks should be written as with_items including the quotation marks around the double braces -> :"{{ user_name }}".

 

Host based variables and groupbased variables

 As the names denote, host variables apply to a specific host, while group variables apply to all hosts in a host group or group of groups. Host variables will take precedence over any group variables supplied on a host, but variables defined inside a play will then override either of these. You can define both host and group variables in the inventory itself or in subdirectories that contain YAML files that match the names of the host in a host_vars subdirectory or group in a group_vars subdirectory. These YAML files will then contain the list of variables you wish applied with those scopes. Variables defined in the host_vars and group_vars directory have a higher precedence than those defined as inventory variables.

 

To utilize this technique, you'll need to create directories at the same level as your Ansible playbook. Creating the two directories, group_vars and host_vars, will allow you areas to provide YAML files to define variables with this technique. If we had a group defined an inventory named servers, we could then create a subdirectory group_vars that contains the YAML file, servers. Any variables we define in the servers file will then be supplied as variables on all hosts in the servers group. In the example to the right, we see proper YAML syntax for setting variables in this fashion.

ansible_user: devops

newfiles:

  • /tmp/a.conf
  • /tmp/b.conf

The ansible_user variable is set to the string devops, while the newfiles variable is a list of two different values. If you wish to create variables for a specific host with this technique, create a host_vars directory and contain those variables and a YAML file that matches the host's name.

 

Here's a look at a proper file hierarchy that has examples of this technique.


For example, we could reference the users variable and then the aditya user's username and then their first name by the syntax users, opening the bracket, opening the quotation mark, and naming the username Aditya, and then following that with an open bracket and open quotation mark for the fname.



In a similar fashion, we can get Carlotta's home directory reference with users open bracket, open quotation, Carlotta. Close both of those. And then open bracket, open quotation home.



Register Statement

The register statement will allow us to capture the output of a task and store it in a variable during execution. The output is saved into a temporary variable that could be used throughout the rest of the playbook for either debugging or utilization for another task.

 

This is a common technique that allows us to take advantage of the return values from each module, store them in a variable, and reuse them throughout the rest of our workloads.

These registered variables are only stored in memory and are destroyed once playbook execution completes.

 

From our previous play, let's have a look at what it looks like currently.


We can evolve the value that we're supplying for the username test as a variable at the top of the play.

Alright, up here in the heading keys, we can add a new key for vars. Since this is a child, we'll need to twospace indent beneath it, but we'll simply supply the vars. Let's create a variable named username. We'll set this user name to test. We'll then utilize it down below in the play by doing variable substitution. Since we're substituting a variable, we'll needs a first enclose quotation marks, the double braces, and include the variable name in the middle here, username. 



ansible-playbook example.yml

 

Let's supply a command line variable substitution.

ansible-playbook -e “username=student”

We'll say ansibleplaybook and, providing extra variables, we can supply username and set it equal to a second username. It will add both test user and student user.

Oftentimes, we may want the variable to contain a list of values. For example, we may have additional information we want to supply. We'll transform the username variable into more of a dictionary of values. Here, I'll remove test for now and then begin building child values underneath username. Each of these child keys will be indented two spaces further. And as we provide more values for the test user, we can then further indent two additional spaces. (Working) Now that we've evolved our variable, we'll need to update the interpolation below. The notation we'll use here will involve brackets and single quotation marks to iterate through the fields. Let's also take advantage of that new value that we've supplied. The user module also provides a key comment to allow for additional commentary within the etc/passwd file.


We've previously taken a look at the host_vars concept, but let's also evolve our playbook to take advantage of that technique. I'm going to go ahead and create the group_vars alongside the host_vars directory. This is what we currently have.



To clean up our work for further exercises, I'm going to go ahead and remove the db01 host variables we previously set by simply deleting that file. After that clean up, we have the current structure in place.


Since we've been performing the work on the webservers group, we can create a file in the group_vars directory for the webservers group. We can migrate the variables we just created into that file and take advantage from the playbook in the exact same fashion.

Let's create the file group_vars/webservers. In this file, we'll simply paste our variable information from playbook.



 It can be helpful to provide a comment at the beginning of each file, so we understand what the file's intention is. In example.yml remove those values. Since we have no additional variables currently, we can leave the key and have it blank. But since we don't provide variables, I'll go ahead and remove it as well. It's best to keep your playbooks as clean as possible.



Ansible-playbook example.yml

 

 

Ansible Vault:

 

Create a new encrypted file:

ansible-vault create filename

 

View an encrypted file

ansible-vault view filename

 

Edit an encrypted file

ansible-vault edit filename

Encrypt existing file

ansible-vault encrypt filename

 

Save to new file

--output=new_filename

 

Decrypt file

ansible-vault decrypt filename

 

Now that we have encrypted information, we'll want to use that within our playbooks. We can provide the vault password that we set when encrypting the file with the ‑‑vault id option. You can see an example command

ansibleplaybook ‑‑vault id @prompt filename

 

The @prompt option ensures that Ansible understands it needs to receive user input for the password. If you do not provide that password, Ansible will return an error.

You may have different passwords for various files that are encrypted using Ansible Vault. When you need to supply multiple passwords, we'll have to understand the technique that allows us to do that. Using the ‑‑vault id option, we can set labels on the encrypted file. We can then use this as many times as necessary to label the various files we have encrypted and ask Ansible to prompt us for the different passwords when we need to supply them. Have a look at this last example.

ansibleplaybook ‑‑vault id vars@prompt –vault-id playbook@prompt site.yml

Ansibleplaybook calls the vaultid and supplies a vars@prompt argument. It calls it again, providing a playbook@prompt argument before then calling the playbook site.yml. Ansible will then prompt you with this execution for two different passwords, one for vars and one for playbook. Given that you provide the two appropriate passwords, the files will be decrypted when utilized by the playbook, and execution will proceed; else Ansible will provide an error.

 

Once we've created a password, we may need to change that on an encrypted file.

Ansible-vault rekey filename


To change the password of an encrypted file, we'll use the subcommand rekey for the Ansible Vault command. You can use this subcommand on multiple data files at once, providing a helpful way to rekey a bunch of files to the same password. The rekey subcommand will prompt for the current password and then the new password you wish to set for these encrypted files.

While we're discussing sensitive information, sometimes Ansible output can include sensitive values. When this is the case, you may want to suppress the output from a given task that could do that. When we want to suppress that output, we can use the key no_log. By using this value, Ansible will suppress the output of the task so that sensitive information is not displayed. Have a look at these two examples.



 


Golang - Email - Secure code warrior

 package mail import ( "net/smtp" "gobin/config" ) var ( emailConfig config.Email ) type Mail struct { Destinati...