Is there a way to use the ansible.builtin.uri module to post / put an encrypted file while seamlessly decrypting it from the vault? Or is there a safe workaround (i.e. a secure sequence of tasks?).
The use case is to upload a licence file which is stored encrypted with ansible vault in the roles/the_role/files folder of a project.
The ansible.builtin.uri module is able to find the encrypted file, but it does not decrypt it before the upload.
- name: "Nexus Update License: Uploading new License file"
ansible.builtin.uri:
url: "http://{{ inventory_hostname }}:{{ nexus_default_port }}{{ nexus_default_context_path | regex_replace('\\/$', '')}}/service/rest/v1/system/license"
user: "{{ nexus_admin_account }}"
password: "{{ nexus_admin_password }}"
headers:
Content-Type: application/octet-stream
method: POST
force_basic_auth: yes
status_code: 200,204
src: "license.lic.enc" # this uploads the license still encrypted...
This question is similar, but I cannot use the copy module:
How to upload encrypted file using ansible vault?
I wasn't able to find a way to upload a file while decrypting it from the vault on the fly.
One workaround is to upload the file to the remote host, using it and then being sure it is removed in any case.
It is better than decrypting the file on the host running ansible as other users might have access to it, while the task performed by ansible should be quite quick.
# The following is slightly better as it will remove the license after use
- name: "Deploy new license"
block:
- name: "Copy license file"
ansible.builtin.copy:
src: "{{ nexus_license_file }}"
dest: "/tmp/license"
owner: "{{ nexus_os_user }}"
group: "{{ nexus_os_group }}"
mode: 0400
- name: "Nexus Update License ({{ ansible_hostname }}): Uploading new License file"
ansible.builtin.uri:
url: "http://{{ inventory_hostname }}:{{ nexus_default_port }}{{ nexus_default_context_path | regex_replace('\\/$', '')}}/service/rest/v1/system/license"
user: "{{ nexus_admin_account }}"
password: "{{ nexus_admin_password }}"
headers:
Content-Type: application/octet-stream
method: POST
force_basic_auth: yes
status_code: 200,204
src: "/tmp/license"
remote_src: true
always: # Always remove the license file
- name: "Remove license file"
ansible.builtin.file:
path: "/tmp/license"
state: absent
Related
I'm currently using an Ansible playbook to extract and then transfer a configuration backup from some network devices (a basic text file) to an external storage.
I'd like to encrypt the configuration backups before sending them to their final storage. What would be the most adequate way to encrypt a file from inside an Ansible playbook task? To me, the obvious way would be to use the shell module to either call an external encryption tool (openssl) or an ansible-vault command to encrypt the backup in a format that ansible itself can read later in some other context; i.e. one of the two tasks below (simplified):
- name: Encrypt stuff with OpenSSL using a password read from vault file
shell:
cmd: openssl {{ openssl_parameters }} -k {{ vaulted_encryption_password }} -in {{ file_to_encrypt }} -out {{ encrypted_file }}
- name: Encrypt stuff with Ansible-Vault
shell:
cmd: ansible-vault encrypt {{ file_to_encrypt }} --vault-password-file {{ vault_password_file }}
However, none of these solutions seem completely secure, given they require passing the encryption password to an external tool via a shell (which can expose the password to anyone monitoring the processes on the host this runs in, for example) or require writing the plain-text password on a file for ansible-vault to use.
Is there a better way of doing file encryption inside an Ansible task that I'm missing here? (a dedicated module, or some other solution?).
Updated answer valid since ansible 2.12
The original answer below was one solution until the availability of ansible-core v2.12. Since then, there is a new ansible.builtin.vault filter which make this much easier.
Here is a complete test (whihc needs to be hardened for complete security of course...)
First, we create a secret.txt file we later want to encrypt:
echo "I'm a file that needs to be encrypted" > secret.txt
Then the playbook encrypt.yml:
---
- hosts: localhost
gather_facts: false
vars:
vault_file: secret.txt
vault_secret: v3rys3cr3t
tasks:
- name: In-place (re)encrypt file {{ vault_file }}
copy:
content: "{{ lookup('ansible.builtin.file', vault_file) | ansible.builtin.vault(vault_secret) }}"
dest: "{{ vault_file }}"
decrypt: false
Gives:
$ ansible-playbook encrypt.yml
PLAY [localhost] ***********************************************************************************************************************************************************************************************************************
TASK [In-place (re)encrypt file secret.txt] ********************************************************************************************************************************************************************************************
changed: [localhost]
PLAY RECAP *****************************************************************************************************************************************************************************************************************************
localhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
And we can now check the file was effectively encrypted and still contains the original data
$ ansible-vault view --ask-vault-pass secret.txt
Vault password:
I'm a file that needs to be encrypted
(END)
Note that the above playbook is not idempotent. If you replay the tasks again:
you will have to provide the current vault password to ansible so that the file lookup can read the content.
the file will be changed even if the decrypt and encrypt password are identical.
Previous answer kept for history and still valid for ansible < 2.12
There are no modules I know to use ansible-vault from playbooks directly (besides the obvious intended use which is to decrypt variables and file contents on the fly).
One possible way to improve security (as far as listing processes is concerned) with your ansible-vault example through a command would be to use the interactive prompt mode and fill the password with the expect module. An other security layer can be added by adding the no_log: true parameter to the task so it does not print content of the variables.
Here is a simple example (you will need to pip install pexpect on the target host):
---
- hosts: localhost
gather_facts: false
tasks:
- name: Vault encrypt a given file
vars:
vault_pass: v3rys3cur3
expect:
command: ansible-vault encrypt --ask-vault-pass toto.txt
responses:
New Vault password: "{{ vault_pass }}"
Confirm New Vault password: "{{ vault_pass }}"
Which gives (using the verbose mode to illustrate the no_log feature and provided the given file exist and is not yet encrypted...):
$ ansible-playbook -v test.yml
No config file found; using defaults
PLAY [localhost] **************************************************************************************************************************************************************************************************
TASK [Vault encrypt a given file] *********************************************************************************************************************************************************************************
changed: [localhost] => {"censored": "the output has been hidden due to the fact that 'no_log: true' was specified for this result", "changed": true}
PLAY RECAP ********************************************************************************************************************************************************************************************************
localhost : ok=1 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
I'm using an ansible playbook (ansible ver. 2.9) to install WordPress using wp-cli tool.
Here's the playbook:
- name: Create WordPress database
mysql_db: name="{{ db_name }}"
state=present
login_user=root
login_password="{{ mysql_root_password }}"
- name: Create WordPress DB user and grant permissions to WordPress DB
mysql_user: name="{{ db_user }}"
password="{{ db_pwd }}"
priv="{{ db_name }}.*:ALL"
state=present
login_user="root"
login_password="{{ mysql_root_password }}"
- name: Is WordPress downloaded?
stat: path="/var/www/{{ domain_name }}/html/index.php"
register: wp_dir
- name: Download WordPress
command: wp core download
args:
chdir: "/var/www/{{ domain_name }}/html/"
remote_user: "{{ web_user }}"
when: wp_dir.stat.isdir is not defined
- name: Configure WordPress
command: wp core config
--path="/var/www/{{ domain_name }}/html"
--dbname="{{ db_name }}"
--dbuser="{{ db_user }}"
--dbpass="{{ db_pwd }}"
--dbprefix="{{ db_prefix }}"
remote_user: "{{ web_user }}"
when: wp_dir.stat.isdir is not defined
- name: Is WordPress installed?
command: wp core is-installed
args:
chdir: "/var/www/{{ domain_name }}/html/"
register: wordpress_is_installed
ignore_errors: True
remote_user: "{{ web_user }}"
- name: Install WordPress tables
command: wp core install
--url="{{ wp_home_url }}"
--title="{{ wp_site_title }}"
--admin_user="{{ wp_admin_user }}"
--admin_password="{{ wp_admin_pwd }}"
--admin_email="{{ wp_admin_email }}"
args:
chdir: "/var/www/{{ domain_name }}/html/"
when: wordpress_is_installed|failed
remote_user: "{{ web_user }}"
At the "Download WordPress" task, a fatal error shows up:
"Error: YIKES! It looks like you're running this as root. You probably meant to run this as the user that your WordPress installation exists under."
I run the playbook as a sudo user ("ansible_user" in hosts file). And I have setup an additional user to manage WordPress setup (remote_user: "{{ web_user }}").
Any help would be much appreciated!
In the tasks you need to use become and become_user instead remote_user as below
- name: Download WordPress
command: wp core download
args:
chdir: "/var/www/{{ domain_name }}/html/"
become: yes
become_user: "{{ web_user }}"
when: wp_dir.stat.isdir is not defined
Now a different error is showing up when running the same code:
FAILED! => {"msg": "Failed to set permissions on the temporary files Ansible needs to create when becoming an unprivileged user (rc: 1, err: chown: changing ownership of '/var/tmp/ansible-tmp-1613648876.307028-8235-221563540981220/': Operation not permitted\nchown: changing ownership of '/var/tmp/ansible-tmp-1613648876.307028-8235-221563540981220/AnsiballZ_command.py': Operation not permitted\n}). For information on working around this, see https://docs.ansible.com/ansible/become.html#becoming-an-unprivileged-user"}
I updated Ansible to the last version available (2.10).
The only solution I've found so far is adding allow_world_readable_tmpfiles = Yes to ansible.cfg file...
Any ideas?
Thanks
[☺ first time posting here, I have huge problems with formating so sorry, I really dont understand how to get that code to the grey boxes, sorry!)
Hello, so I am supposed to set up a server using Ansible for a high school graduation project. All I have to do is basicaly install a few programs like htop, httpd ..... and finally set up a wordpress server. I am folowing this guide.
Problem is that this code:
---
# tasks file for wp-dependencies
- name: Update packages (this is equivalent to yum update -y)
yum: name=* state=latest
- name: Install dependencies for WordPress
yum:
name:
- php
- php-mysql
- MySQL-python
state: present
- name: Ensure MariaDB is running (and enable it at boot)
service: name=mariadb state=started enabled=yes
- name: Copy ~/.my.cnf to nodes
copy: src=.my.cnf dest=/root/.my.cnf
- name: Create MariaDB database
mysql_db: name={{ wp_mysql_db }} state=present
- name: Create MariaDB username and password
mysql_user: login_user=root login_password=root name = {{ wp_mysql_user }} password = {{ wp_mysql_password }}
priv=*.*:ALL`
Results in this error:
TASK [wp-dependencies : Create MariaDB username and password] ******************************************
fatal: [192.168.56.101]: FAILED! => {"changed": false, "msg": "missing required arguments: user"}
to retry, use: --limit #/home/Admin/wordpress.retry
COuld you tell whats the problem?
Your task is this:
- name: Create MariaDB username and password
mysql_user: login_user=root login_password=root name = {{ wp_mysql_user }} password = {{ wp_mysql_password }}
priv=*.*:ALL`
You have spaces between name and password and the values they are to take. And for safe variable handling you should also place quotation marks (") around the variables.
Try this:
- name: Create MariaDB username and password
mysql_user: login_user=root login_password=root name="{{ wp_mysql_user }}" password="{{ wp_mysql_password }}" priv=*.*:ALL
In my ansible playbooks, I often have steps like "create a directory and then do something in it", e.g.:
- name: Create directory
file:
path: "{{ tomcat_directory }}"
state: directory
- name: Extract tomcat
unarchive:
src: 'tomcat.tar.gz'
dest: '{{ tomcat_directory }}'
When I run this playbook, it works perfectly fine. However, when I run this playbook in check mode, the first step succeeds (folder would have been created), but the second one fails, because the folder does not exist.
Is there any way how I could write steps like these where I create folder and then operate in it while also being able to run the playbook in check mode (without skipping such steps)?
Check mode can be a bit of a pain. You only really have two options:
1) Add conditionals to tasks to skip them in check mode, which you don't want to do. For reference tho:
when: not ansible_check_mode
2) You can change the behaviour of the task in check mode. If you set check_mode: no on a task, then in check mode it will behave as it would in a normal run. That is to say, despite you specifying check mode, it will actually perform the task and create the dir if it does not already exist. You have to make a choice if you are happy for a given task to run for real in check mode, so it tends to only be appropriate for low risk tasks, but does provide you a route to continue testing the rest of your playbook that is dependent on the step in question.
Ansible Check Mode Docs
You could make use of the ignore_errors task option, along with the ansible_check_mode variable, to ignore errors with your Extract tomcat task only when running in check mode, e.g.:
- name: Create directory
file:
path: "{{ tomcat_directory }}"
state: directory
- name: Extract tomcat
unarchive:
src: 'tomcat.tar.gz'
dest: '{{ tomcat_directory }}'
ignore_errors: "{{ ansible_check_mode }}"
Running this in check mode will show the Extract tomcat task failed due to dest not existing. However, instead of failing the playbook, the task failure will be marked as ignored and playbook execution will continue.
An option would be to "register: result" and test "when: result.state is defined"
- name: Create directory
file:
path: "{{ tomcat_directory }}"
state: directory
register: result
- name: Extract tomcat
unarchive:
src: 'tomcat.tar.gz'
dest: '{{ tomcat_directory }}'
when: result.state is defined
I am trying to using the ntc-ansible module with Ansible running on Ubuntu (WSL). I have ssh connectivity to my remote device (Cisco 2960X) and I can run ansible playbooks to the same remote switch using the built in Ansible networking modules (ios_command) and it works fine.
Issue:
When I try to run any of the ntc-ansible modules, it fails, unable to connect to the device. Probably something simple, but I have hit a wall. There is something I am missing about how to use ntc-ansible modules. Ansible is seeing the modules as I can look at the docs as was suggested as a test in the readme.
I have ntc-ansible module installed here: /home/melshman/.ansible/plugins/modules/ntc-ansible
I am running my playbooks from here: ~/projects/ansible/
The first time I ran the playbook with the ntc-ansible modules it failed and based on error message and some research I installed sshpass (sudo apt-get install sshpass). But still having ssh problems using ntc-ansible… (playbook and traceback below)
I hear folks taking about an index file, but I can’t find that file? Where does it live and what do I need to do with it?
What is my connection supposed to be setup to be? Local? SSH? Netmiko_ssh?
What should I be using for platform? Cisco_ios? cisco_ios_ssh?
Appreciate any help I can get. I have been running in circles for hours and hours.
Ansible Version Info:
VTMNB17024:~/projects/ansible $ ansible --version
ansible 2.5.3
config file = /home/melshman/projects/ansible/ansible.cfg
configured module search path = [u'/home/melshman/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
ansible python module location = /usr/local/lib/python2.7/dist-packages/ansible
executable location = /usr/local/bin/ansible
python version = 2.7.12 (default, Dec 4 2017, 14:50:18) [GCC 5.4.0 20160609]
Working playbook (ios_command:) note: ansible_ssh_pass and ansible_user in group var:
- name: Test Net Automation
hosts: ctil-ios-upgrade
connection: local
gather_facts: no
tasks:
- name: Grab run config
ios_command:
commands:
- show run
register: config
- name: Create backup of running configuration
copy:
content: "{{config.stdout[0]}}"
dest: "backups/show_run_{{inventory_hostname}}.txt"
Playbook (not working) using ntc-ansible module (Note: username and password are defined in Group VAR:
- name: Cisco IOS Automation
hosts: ctil-ios-upgrade
connection: local
gather_facts: no
tasks:
- name: GET UPTIME
ntc_show_command:
connection: ssh
platform: "cisco_ios"
command: 'show version | inc uptime'
host: "{{ inventory_hostname }}"
username: "{{ username }}"
password: "{{ password }}"
use_templates: True
template_dir: /home/melshman/.ansible/plugins/modules/ntc-ansible/ntc-templates/templates
Here is the traceback I get when the error occurs:
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: netmiko.ssh_exception.NetMikoTimeoutException: Connection to device timed-out: cisco_ios VTgroup_SW:22
fatal: [VTgroup_SW]: FAILED! => {"changed": false, "module_stderr": "Traceback (most recent call last):\n File \"/tmp/ansible_RJRY9m/ansible_module_ntc_save_config.py\", line 279, in \n main()\n File \"/tmp/ansible_RJRY9m/ansible_module_ntc_save_config.py\", line 251, in main\n device = ntc_device(device_type, host, username, password, **kwargs)\n File \"/usr/local/lib/python2.7/dist-packages/pyntc-0.0.6-py2.7.egg/pyntc/__init__.py\", line 35, in ntc_device\n return device_class(*args, **kwargs)\n File \"/usr/local/lib/python2.7/dist-packages/pyntc-0.0.6-py2.7.egg/pyntc/devices/ios_device.py\", line 39, in __init__\n self.open()\n File \"/usr/local/lib/python2.7/dist-packages/pyntc-0.0.6-py2.7.egg/pyntc/devices/ios_device.py\", line 55, in open\n verbose=False)\n File \"build/bdist.linux-x86_64/egg/netmiko/ssh_dispatcher.py\", line 178, in ConnectHandler\n File \"build/bdist.linux-x86_64/egg/netmiko/base_connection.py\", line 207, in __init__\n File \"build/bdist.linux-x86_64/egg/netmiko/base_connection.py\", line 693, in establish_connection\nnetmiko.ssh_exception.NetMikoTimeoutException: Connection to device timed-out: cisco_ios VTgroup_SW:22\n", "module_stdout": "", "msg": "MODULE FAILURE", "rc": 1}
Here is a working solution using ntc_show_command to a Cisco IOS device.
- name: Cisco IOS Automation
hosts: pynet-rtr1
connection: local
gather_facts: no
tasks:
- name: GET UPTIME
ntc_show_command:
connection: ssh
platform: "cisco_ios"
command: 'show version'
host: "{{ ansible_host }}"
username: "{{ ansible_user }}"
password: "{{ ansible_ssh_pass }}"
use_templates: True
template_dir: '/home/kbyers/ntc-templates/templates'
If you are going to use ntc-templates, I probably would not have the '| include uptime' in the 'show version'. In other words, let TextFSM convert the output to structured data first and then grab the uptime from that structured data.
I modified inventory_hostname to ansible_host to be consistent with my inventory format (my inventory_hostname doesn't actually resolve in DNS).
I modified username and password to 'ansible_user' and 'ansible_ssh_pass' to be consistent with my inventory and also to be more consistent with Ansible 2.5/2.6 variable naming.
On your above issue, your exception message does not match your playbook (i.e. are you sure that is the exception you get for that playbook).
Here is my inventory file (I simplified this to remove some unnecessary devices and to hide confidential information)
[all:vars]
ansible_connection=local
ansible_python_interpreter=/home/kbyers/VENV/ansible/bin/python
ansible_user=user
ansible_ssh_pass=password
[local]
localhost ansible_connection=local
[cisco]
pynet-rtr1 ansible_host=cisco1.domain.com
pynet-rtr2 ansible_host=cisco2.domain.com