Prevent usage of wrong Ansible inventory - ansible

Imagine a server setup with Ansible with a production and a reference system/cluster and a separate server running Ansible (with ssh-keys). The different clusters are identified in two inventory files.
Every playbook usage will somehow look like ansible-playbook -i production ... or ansible-playbook -i reference....
How do I prevent an accidental usage of the production inventory?
This could happen easily by either using an history entry in the shell or copy the command from some documentation.
Some ideas:
As a start every documentation is referring to the reference inventory and is also using --check.
Use two different Ansible instances and the common part is mirrored via Git. But this will result in some overhead.
Prompt once for i.e. database passwords and use different on production and reference. But not all task/tags have such a password requirement.
Actually I'm looking for something like a master password when using a specific inventory? Or a task that is always executed even if a tag is used? What is the best practice here? Do you have other ideas? Or am I somehow totally wrong here and there is a better way for my scenario?

Your production inventory could either be vaulted or even just include a vaulted file (which doesn't actually have to contain anything).
This will mean that when you attempt to run:
ansible-playbook -i production playbook.yml
It will fail with a message telling you to pass a vault password to it.
The vault password could be something simple such as "pr0duct10n" so it's entire purpose is to remind people that they are about to run it in production and so to think things through (much like how many people use a sudo password).

Related

How to set up a playbook to use vault when run from engine, and to skip vault when run from Tower/AWX

I was unable to find documentation or Q&A covering this specific topic. I'm posting it here with the answer in the hopes that someone finds it useful. I am a member of a small group of automation engineers. One task is to provide automation in Tower for other engineers and admins.
The automation team is happy with our current setup which allows us to run plays from the commandline without locking our accounts, without typing a password every time, and with no passwords stored in plain text:
We use an ansible vault stored in each admin's profile to store encrypted login credentials, along with a gpg armored key. Each vault uses teh same name, similar to ~/.ansible/vault.yml
A script extracts the key and unlocks the vault.
The script is defined in ansible.cfg, [defaults], vault_identity_list.
the playbooks load the vault with vars_files
Tower in job isolation mode cannot access home directories. And we do not want vault+key outside the admin's home folder, subject to random prying. Tower has its own vault system that we use when using tower. We want to maintain our current method of commandline runs, but be able to use the same playbook in Tower and Engine.
I tried:
Forcing tower to read the vault. (no joy)
Playbook with vault commented out. (This worked in tower, but I had to toggle the commenting to run from commandline. Put a pin in this as a last resort.)
Using conditional to only load vars_files when ansible_user is not awx. (Well guess what, it still runs as the user who triggers the job. Put a pin in this to find another variable that is consistent and indicates tower is the platform.)
Using tags and skip-tags within tower to skip vars_files (no joy. Tags don't work on vars_files:)
What I found that worked:
skip-tags does exactly what I needed to do
learned about include_vars (this is a task module that can be tagged)
learned about pre_tasks (since we're including the become credentials in the vault, regular tasks would never be reached because 'no SUDO credentials' would prevent tasks from being run)
so:
pre_tasks:
- include_vars: ~/.ansible/vault.yml
tags: engine
and, in Tower, set the job template to skip-tags: engine
Now the same play works in or out of Tower. With minimal authentication. Without plaintext passwords.

How to differentiate between staging/production with a dynamic inventory?

I'm stuck. Googled the hell out of the Web and couldn't find an answer.
I've been using Ansible for years, but always with static inventories. To differentiate between different environments like staging and production, I used different static inventory files, staging and production, respectively. When I needed to provision staging servers, I'd do:
ansible-playbook site.yml -i staging
When I wanted to do the same for production, I'd do:
ansible-playbook site.yml -i production
Both staging and production need variables with different values, so I have group_vars/staging and group_vars/production. All good and according to best practices.
Now, I need to provision EC2 instances in AWS. I'm using this AWS guide. I have a playbook with two plays. The first is run against localhost, creates/finds required EC2 instances in AWS, and populates a group with add_host. The second play uses that group to run against the EC2 instances discovered in the first play. All according to that guide.
It all works great except one thing. I have no idea how to specify which environment to provision and hence the required variables are not being loaded from group_vars/(staging|production). Basically, what I want is something similar to -i (staging|production) I used all these years with static inventories, but it seems that using -i doesn't make sense now since the inventory is dynamic. I want a way to be able to load variables from either group_vars/staging or group_vars/production based on an argument I pass to ansible-playbook when I run it.
How do I do that? What's the best practice?
While I am not sure how to do it with ansible EC2 moduel as we don't use it to build boxes from ansible level, there is a simple way to get what you want with ec2 external inventory script and simple settings in your inventories/main. What you need to do is set up the ec2.py and ec2.ini inside of your inventories so it will be used as source of instances. Make sure to uncomment group_by_tag_keys = True inside of ec2.ini.
Next step is to differentiate which instance goes where. While there are many selection methods available in ec2.py, I prefer to specifically tag each instance accordingly. So all my instances have a tag called environment which is filled accordingly (in your case it would be either staging or production). Then all is left is to handle it inside of your inventories/main, and here is a small example how to do it.
First you must define empty group for tags you want to use:
[tag_environment_staging]
[tag_environment_production]
so we can later reference to them. After that all there is left to do is specify those groups as children for appropriate stages. So after that our minimal file will look like that:
[tag_environment_staging]
[tag_environment_production]
[staging:children]
tag_environment_staging
[production:children]
tag_environment_production
And there you go. From now on every instance pulled from ec2 via dynamic inventory script that comes with environment tag will be matched to appropriate config in group_vars. All you have to remember that when dealing with dynamic inventories you want your -i point at inventories directory rather than specific file for it to work right.
I have a similar problem with dynamic inventories but for Openstack. The solution I've come up with so far is to use an environment variable to specify whether I want to target the staging or production environment. It should be applicable to your case as well. In our setup $OS_PROJECT_NAME is either stage or prod. In ansible.cfg set
inventory = ./inventories/${OS_PROJECT_NAME}/openstack.py
Then we have environment specific group variables under
inventories/(stage|prod)/group_vars/
The drawback is you have to have the inventory script in two places or have it symlinked. Beware also that group_vars found relative to the playbook directory will still override the inventory group_vars.

How can I speedup ansible by not running entire sections too often?

Assume that a normal deployment script does a lot of things and many of them are related to preparing the OS itself.
These tasks are taking a LOT of time to run even if there is nothing new to do and I want to prevent running them more often than, let's say once a day.
I know that I can use tags to filter what I am running but that's not the point: I need to make ansible aware that these "sections" executed successfully one hour ago and due to this, it would skip the entire block now.
I was expecting that caching of facts was supposed to do this but somehow I wasnt able to see any read case.
You need to figure out how to determine what "executed successfully" means. Is is just that a given playbook ran to completion? Certain roles ran to completion? Certain indicators exist that allow you determine success?
As you mention, I don't think fact caching is going to help you here unless you want to introduce custom facts into each host (http://docs.ansible.com/ansible/playbooks_variables.html#local-facts-facts-d)
I typically come up with a set of variables/facts that indicate a role has already been run. Sometimes this involves making shell calls and registering vars, looking at gathered facts and determining if certain files exist. My typical pattern for a role looks something like this
roles/my_role/main.yml
- name: load facts
include: facts.yml
- name: run config tasks if needed
include: config.yml
when: fact_var1 and fact_vars2 and inv_var1 and reg_var1
You could also dynamically write a yaml variable file that get's included in your playbooks and contains variables about the configured state of your environment. This is a more global option and doesn't really work if you need to look at the configured status of individual machines. An extension of this would be to write status variables to host_vars or group_vars inventory variable files. These would get loaded automatically on a host by host basis.
Unfortunately, as far as I know, fact caching only caches host based facts such as those created by the setup module so wouldn't allow you to use set_fact to register a fact that, for example, a role had been completed and then conditionally check for that at the start of the role.
Instead you might want to consider using Ansible with another product to orchestrate it in a more complex fashion.

Does Ansible vault have to use a password to run

I have been looking into Ansible vault but want to check something incase I have missed a crucial point.
Do you have to run the playbook and provide the password. Encrypting the data seems a great idea but if I share the playbook the person running it will require the password. If they have the password then they can decrypt the file and see the data.
I would like to use it to set passwords for files but would like non admins to be able to run the playbook.
Have I missed something. I am struggling to see its worth if this is the case.
Thanks
The purpose of the vault is to keep secrets encrypted "at rest" (eg, in your source control repo, on-disk), so that someone can't learn the secrets by getting ahold of the content. As others have mentioned, if you want to delegate use of the secrets without divulging them, you'll need an intermediary like Tower.
In your case you need something that will be brokering ansible execution. Because like you've said an encryption would be useless if you share the password.
Like it's mentioned in the comment you can use Ansible Tower, or you can try and set a simple http endpoint that will be trigerring ansible based on specified parameters.

control ansible task file execution

My current Ansible project is setup like so:
backup-gitlab.yml
roles/
aws_backups/
tasks/
main.yml
backup-vm.yml
gitlab/
tasks/
main.yml
start.yml
stop.yml
backup-gitlab.yml needs to do the following:
Invoke stop.yml on the gitlab host.
Invoke backup-gitlab.yml on a different host.
Invoke start.yml on the gitlab host.
The problem I'm running into is Ansible doesn't seem to support a way of choosing which task files to run within the same role in the same playbook. Before I was using tags to control what Ansible would do, but in this case tagging the include statements for start.yml and stop.yml doesn't work because Ansible doesn't appear to have a way to dynamically change the applied tags that are run once they are set through the command line.
I can't come up with an elegant way to achieve this.
Some options are:
Have each task file be contained within its own role. This is annoying because I will end up with a million roles that are not grouped in any way. It's essentially abandoning the whole 'role' concept.
Use include with hard coded paths. This is prone to error as things move around. Also, since Ansible deprecated combining with_items with include (or using any sort of dynamic looping with include), I can no longer quickly change up the task files being run. Any minor change in my workflow requires lots of coding changes. I would really like to stick with using tags from the command line to control exactly what Ansible does.
Use shell scripts to invoke separate Ansible playbooks.
Use conditionals (when clause) on every single Ansible action, and control what gets run by setting variables. While several people have recommended this on SO, it sounds awful. I will have to add the conditional to hundreds of actions and every time I run a playbook the output will be cluttered by hundred's of 'skip' statements.
Leverage Jinja templates and ansible's local_connection to dynamically build static main.yml files with all the required task files included in the proper order (using computed relative paths). Then invoke that computed main.yml file. This is dangerous and convoluted.
Use top level Ansible plays to invoke lower level plays. Seems messy, also this brings in problems when I need to pass variables between plays. Using Ansible's Python Api may help this.
Ansible strives to bring VMs into idempotent states but this isn't very helpful and is a dated way of thinking in my opinion (I would have stuck with Chef if that is all I wanted). I want to leverage Ansible to actually do things such as: actively change configuration states, kick off processes, monitor events, react to events, etc. Essentially I want it to automate as much of my job as possible. The current 'role' structure (with static configurations) that Ansible recommends doesn't fit this paradigm very well even though their usage of remote command execution via SSH gets us so close to the dream.
Just use a playbook for these types of management tasks.
Granted the skip statements do somewhat clutter the output. If you wish to fix that you can further breakdown the roles into something like aws_backups-setup and aws_backups-managment.
Also the roles documentation has some information on how you can run pre_tasks and post_tasks on roles.

Resources