Ansible: Handling temporary file idempotently and securely - ansible

In Ansible (RHEL 8 if it matters), I need to create a temporary file from a template with sensitive content. After a few other tasks are completed, it should be deleted. The temporary file is an answerfile for an installer that will run as a command. The installer needs a user name and password.
I can't figure out if there is a way to do this easily in Ansible.
The brute-force implementation of what I'm looking for would look similar to this:
- name: Create answer file
template:
src: answerfile.xml.j2
dest: /somewhere/answer.xml
owner: root
group: root
mode: '0600'
- name: Install
command: /somewhere/myinstaller --answerfile /somewhere/answer.xml
creates: /somewhereelse/installedprogram
- name: Delete answerfile
file:
path: /somewhere/answer.xml
state: absent
Of course, this code is not idempotent - the answer file would get created and destroyed on each run.
Is there a better way to do this?

Test the existence of the file. If it exists skip the block. For example
- stat:
path: /somewhereelse/installedprogram
register: st
- block:
- name: Create answer file
template:
src: answerfile.xml.j2
dest: /somewhere/answer.xml
owner: root
group: root
mode: '0600'
- name: Install
command: /somewhere/myinstaller --answerfile /somewhere/answer.xml
- name: Delete answerfile
file:
path: /somewhere/answer.xml
state: absent
when: not st.stat.exists
(not tested)
Taking the task "Delete answerfile" out of the block will make the code more secure. It will always make sure the credentials are not stored in the file. The task won't fail if the file is not present.
- stat:
path: /somewhereelse/installedprogram
register: st
- block:
- name: Create answer file
template:
src: answerfile.xml.j2
dest: /somewhere/answer.xml
owner: root
group: root
mode: '0600'
- name: Install
command: /somewhere/myinstaller --answerfile /somewhere/answer.xml
when: not st.stat.exists
- name: Delete answerfile
file:
path: /somewhere/answer.xml
state: absent
(not tested)

I think that this solution potentially represents a slight improvement on your solution.
With this solution the tasks which create and delete the answerfile will be skipped (rather than always run and reporting changed) if the program you're targeting is already installed.
I still don't love this solution as I don't really like skips.
# Try call the installedprogram. --version is arbitrary here.
# --help, or a simple `which installedprogram` could be alternatives.
- name: Try run installedprogram
command: '/somewhereelse/installedprogram --version'
register: installedprogram_exists
ignore_errors: yes
changed_when: False
# Only create answer file if installedprogram is not installed
- name: Create answer file
template:
src: answerfile.xml.j2
dest: /somewhere/answer.xml
owner: root
group: root
mode: '0600'
when: installedprogram_exists.rc != 0
- name: Install
command: /somewhere/myinstaller --answerfile /somewhere/answer.xml
creates: /somewhereelse/installedprogram
# Only delete answer file if installedprogram is not installed
- name: Delete answerfile
file:
path: /somewhere/answer.xml
state: absent
when: installedprogram_exists.rc != 0

Related

Better way to install helm with Ansible?

The following recipe works, but I find the number of tasks being high and I'm wondering if there is a better way to install Helm while performing a checksum. Currently, I have to:
Check current installed helm version, if any
Download helm and notify the handler to unarchive the binary
Unarchive the binary, if needed
Delete downloaded file, if needed
roles/node/tasks/main.yaml:
- name: Set current helm version
ansible.builtin.command:
cmd: helm version --client --template={{ "'{{ .Version }}'" }}
changed_when: false
failed_when: false
register: current_helm_version
- name: Download helm archive
ansible.builtin.get_url:
url: https://get.helm.sh/helm-{{ helm_version }}-linux-arm64.tar.gz
checksum: sha256:https://get.helm.sh/helm-{{ helm_version }}-linux-arm64.tar.gz.sha256sum
dest: /tmp
owner: root
group: root
mode: 0644
notify: Unarchive helm binary
when: helm_version != current_helm_version.stdout | default(false)
- name: Flush handlers
ansible.builtin.meta: flush_handlers
- name: Delete helm archive
ansible.builtin.file:
path: /tmp/helm-{{ helm_version }}-linux-arm64.tar.gz
state: absent
roles/node/handlers/main.yaml:
- name: Unarchive helm binary
ansible.builtin.unarchive:
src: /tmp/helm-{{ helm_version }}-linux-arm64.tar.gz
dest: /usr/local/bin
extra_opts: "--strip-components=1"
owner: root
group: root
mode: 0755
remote_src: true
You can do it with one single task:
---
- hosts: localhost
tasks:
- name: Install helm if not exists
unarchive:
src: https://get.helm.sh/helm-v3.11.0-linux-amd64.tar.gz
dest: /usr/local/bin
extra_opts: "--strip-components=1"
owner: root
group: root
mode: 0755
remote_src: true
args:
creates: /usr/local/bin/helm
When /usr/local/bin/helm exists, it will not execute the task.

Ansible get_url Fails If Destination File Does Not Exist

When using get_url, it fails if the destination file does not exist locally. If I us vi and create the file with nothing in it, then the call works and the file is replaced. I added force: yes to encourage it to write out the file, but every attempt failed until I created a dummy file.
Any help is appreciated.
Thank you!
---
- name: Download HashiCorp Installation Archive File
hosts: 127.0.0.1
vars:
vault:
distro: 'linux'
version: '1.5.4'
user:
group: 'rcouch'
name: 'rcouch'
tasks:
# https://releases.hashicorp.com/vault/1.5.4/vault_1.5.4_linux_amd64.zip
- name: Download Binary
get_url:
url: "https://releases.hashicorp.com/vault/{{ vault.version }}/vault_{{ vault.version }}_{{ vault.distro }}_amd64.zip"
checksum: "sha256:https://releases.hashicorp.com/vault/{{ vault.version }}/vault_{{ vault.version }}_SHA256SUMS"
force: yes
dest: "vault_{{ vault.version }}_{{ vault.distro }}_amd64.zip"
owner: "{{ user.group }}"
group: "{{ user.name }}"
mode: 0755
register: vault_download
- name: Display Vault Download
debug:
msg:
- "vault_download: {{ vault_download }}"
The get_url module takes an absolute path in dest. A small excerpt from the module's page. Try an absolute path like /tmp or ./.
| dest | Absolute path of where to download the file to. |
tasks:
- get_url:
url: https://releases.hashicorp.com/vault/1.5.4/vault_1.5.4_linux_amd64.zip
dest: ./vault_1.5.4_linux_amd64.zip

I need to find all files except one or two files in Ansible

I need to find all files except few files. Actually I need to delete them after this.
I used the win_find module and exclude option. But it doesn't exclude it.
My code is as below.
vars:
exclude_files: dontdeletethis.txt
tasks:
- name: Find files
win_find:
paths: C:\temp\Test\fol1
excludes: 'dontdeletethis.txt'
register: log_files
This one worked for me
---
- name: Find log files to delete
win_find:
paths: C:\temp\Test\fol1
register: log_files
- name: Delete
win_file:
path: "{{item.path}}"
state: absent
when: 'dontdeletethis.txt not in item.path'
with_items: "{{log_files.files}}"

Ansible playbook to install NCPA agent on Windows

I am trying to install Nagios NCPA agent on Windows using Ansible play book. Here is my simple playbook
- name: Install NCPA
win_package:
path: https://assets.nagios.com/downloads/ncpa/ncpa-2.1.4.exe
- name: Copy the ncpa.cfg template
win_template:
src: ncpa.cfg.j2
dest: 'C:\Program Files (x86)\Nagios\NCPA\etc\ncpa.cfg'
- name: Restart NCPA
win_service:
name: ncpapassive
state: restarted
However I am getting the below error:
"msg": "product_id is required when the path is not an MSI or the path is an MSI but not local",
How to I find out the product_id for ncpa?
You can skip product_id if you add any of creates_* arguments to your first task, for example:
creates_path: C:\Program Files (x86)\Nagios\NCPA\___main_executable_file__.exe
Or you can search on a machine with your package istalled; per win_package manual:
product_id [ ]
You can find product ids for installed programs in the Windows registry editor either at HKLM:Software\Microsoft\Windows\CurrentVersion\Uninstall or for 32 bit programs at HKLM:Software\Wow6432Node\Microsoft\Windows\CurrentVersion\Uninstall.
This SHOULD be set when the package is not an MSI, or the path is a url or a network share and credential delegation is not being used. The creates_* options can be used instead but is not recommended.
Finally managed to make it work with a dummy product ID.
- name: Create download directory
win_file:
path: C:\\Temp
state: directory
- name: Copy the executable package to download directory
win_copy:
src: ncpa-2.1.4.exe
dest: 'C:\Temp\ncpa-2.1.4.exe'
#- name: Download NCPA executable
# win_get_url:
# url: https://assets.nagios.com/downloads/ncpa/ncpa-2.1.4.exe
# dest: C:\Temp\ncpa-2.1.4.exe
# force: no
# skip_certificate_validation: yes
- name: Install NCPA
win_package:
path: 'C:\Temp\ncpa-2.1.4.exe'
arguments: '/S /TOKEN=demo-token'
product_id: '{ncpa}'
ignore_errors: true
register: installmsi
failed_when: "'was installed' not in installmsi.msg"
- name: Copy the ncpa.cfg template
win_template:
src: ncpa.cfg.j2
dest: 'C:\Program Files (x86)\Nagios\NCPA\etc\ncpa.cfg'
- name: Restart NCPA
win_service:
name: ncpapassive
state: restarted

ansible creating virtualenv with root owner

This Ansible task creates a virtualenv (good!), but the directory (/home/chris/.virtualenvs/foobar) is owned by root (not so good):
- name: install requirements
pip:
chdir: /home/chris/website
requirements: ./requirements.txt
virtualenv: /home/chris/.virtualenvs/foobar
But what's driving me nuts is that the next task fails, apparently because of the root ownership
- name: copy sitecustomize.py
file:
src: /home/chris/website/sitecustomize.py
dest: /home/chris/.virtualenvs/foobar/lib/python2.7/sitecustomize.py
remote_src: yes
I think the problem I really want to solve is creating the .virtualenv so that it's owned by "chris". Any idea how to force that?
Failing that, how can get the "copy" task to run with the same permissions as the "pip" task, so that I can copy my file?
EDIT: Solved second problem -- I needed to use the "copy" task, not the file task. So this...
- name: copy sitecustomize.py
copy:
src: /home/chris/website/sitecustomize.py
dest: /home/chris/.virtualenvs/foobar/lib/python2.7/sitecustomize.py
remote_src: yes
Try to run pip as chris:
- name: install requirements
pip:
chdir: /home/chris/website
requirements: ./requirements.txt
virtualenv: /home/chris/.virtualenvs/foobar
become: yes
become_user: chris
And to be consistent, make chris the owner of sitecustomize.py:
- name: copy sitecustomize.py
file:
src: /home/chris/website/sitecustomize.py
dest: /home/chris/.virtualenvs/foobar/lib/python2.7/sitecustomize.py
remote_src: yes
group: chris
owner: chris

Resources