Does anyone have an example of decrypting and uploading a file using ansible-vault.
I am thinking about keeping my ssl certificates encrypted in source control.
It seems something like the following should work.
---
- name: upload ssl crt
copy: src=../../vault/encrypted.crt dest=/usr/local/etc/ssl/domain.crt
The copy module now does this seamlessly as of Ansible 2.1.x. Just encrypt your file with Ansible Vault and then issue the copy task on the file.
(For reference, here's the feature that added this: https://github.com/ansible/ansible/pull/15417)
UPDATE: Deprecated as of 2016, Ansible 2.1
On any Ansible version prior of 2.1:
That's not going to work. What you will get is your encrypted.crt (with Ansible Vault) uploaded literally as domain.crt
What you need to do is make your playbook part of a "Vault" and add a variable that contains your certificate content. Something like this:
---
- name: My cool playbook
hosts: all
vars:
mycert: |
aasfasdfasfas
sdafasdfasdfasdfsa
asfasfasfddasfasdfa
tasks:
# Apparently this causes new lines on newer ansible versions
# - name: Put uncrypted cert in a file
# shell: echo '{{ mycert }}' > mydecrypted.pem
# You can try this as per
# https://github.com/ansible/ansible/issues/9172
- copy:
content: "{{ mycert }}"
dest: /mydecrypted.pem
- name: Upload Cert
copy: src=/home/ubuntu/mydecrypted.pem dest=/home/ubuntu/mydecrypteddest.pem
- name: Delete decrypted cert
file: path=/home/ubuntu/mydecrypted.pem state=absent
You can choose to put your mycert variable in a separate variable file using Ansible Vault too.
The copy module has been updated in Ansible 2.1. From the changelog:
"copy module can now transparently use a vaulted file as source, if
vault passwords were provided it will decrypt and copy on the fly."
Noting it here, since some people will inevitably not look past the
accepted answer. – JK Laiho
There is a feature request to support this natively in the copy module. But until that is implemented, here is the workaround (similar to #dave1010's answer, but repeating common parts for completeness):
Create a secrets.yml file encrypted with ansible vault which contains your secrets, for example:
---
private_ssl_key: |
-----BEGIN PRIVATE KEY-----
abcabcabcabcabcabcabcabcabc
-----END PRIVATE KEY-----
private_crt: |
-----BEGIN CERTIFICATE-----
abcabcabcabcabcabcabcabcabc
-----END CERTIFICATE-----
In your playbook, include it:
vars_files:
- secrets.yml
Then you can use the variables in tasks:
- name: Copy private kay
copy: content="{{ private_ssl_key }}" dest=/some/path/ssl.key
However, this doesn't work if the file that you are trying to copy is a binary file. In that case, you need to first encode the content with base64:
cat your_secret_file | /usr/bin/base64
Then put the base64 encoded value in your secrets.yml file, e.g.:
crt_b64: |
ndQbmFQSmxrK2IwOFZnZHNJa0sKICAxdDhFRUdmVzhMM...
Then you can create the remote file in two steps:
- name: Copy certificate (base64 encoded)
copy: content="{{ crt_b64 }}" dest=/some/path/cert.b64
- name: Decode certificate
shell: "base64 -d /some/path/cert.b64 > /some/path/cert.txt"
args:
creates: /some/path/cert.txt
Note that you could delete the temporary cert.b64 file on the remote host. But then re-running the playbook will re-create it instead of skipping this task. So, I prefer to leave it there.
UPDATE:
This feature has been implemented in Ansible 2.1.
copy module can now transparently use a vaulted file as source, if
vault passwords were provided it will decrypt and copy on the fly.
Ansible 2.5 added the parameter decrypt to the copy module.
For example, if you encrypted your file using something like:
$ ansible-vault encrypt vault/encrypted.crt
Now you can use copy + decrypt:
---
- name: upload ssl crt
copy:
src: path/to/encrypted-with-vault.crt
dest: /usr/local/etc/ssl/domain.crt
decrypt: yes
mode: 0600
I used a template and a vars_file to do it:
In your top-level playbook:
vars_files:
- secretvars.yml
In a task:
- name: Private ssl key
template: src=etc-ssl-private-site.key dest=/etc/ssl/private/site.key
In the template (etc-ssl-private-site.key) all you need is the variable:
{{ private_ssl_key }}
In the encrypted secretvars.yml (encrypt this with ansible-vault):
---
private_ssl_key: |
-----BEGIN PRIVATE KEY-----
abcabcabcabcabcabcabcabcabc
-----END PRIVATE KEY-----
Update: As of April 2016 my Github PR has been merged and is available in Ansible 2.1 and later. The below was an interim solution until the PR was merged.
Wanting to do the same thing I created an action plugin to implement the feature. This is available via github. The plugin is exactly the copy action plugin as shipped with ansible, but with support for vault decryption.
You can use it like this:
- name: Copy Some Secret File
copyv: src="secret.txt" dest="/tmp/"
if secret.txt is encrypted (and the vault password is supplied) then it will be decrypted and copied.
I think, you have a simpler way to do this.
If you use certificate+key in one file in some format (like pkcs12 or just concatenated), you can use generic openssl (or gpg, or something else) encryption. It will look like this:
openssl enc -e -aes-256-ctr -in original.pem -out encrypted.aes -k <pass-vault>
After that you can just copy encrypted.aes to remote host and decrypt it in-place:
- name: copy encrypted cert and key
copy: src=encrypted.aes dest=/root/ansible-files/ mode=0600
- name: decrypt cert and key
command: openssl enc -aes-256-ctr -d -in /root/ansible-files/encrypted.aes -out <dest> -k {{ pass-vault }}
If you have separate key file in pem or der format, you can use
openssl rsa -in original.pem -out encrypted.pem -aes256 -passout pass:<pass-vault>
Until the 'copy' module has been extended to automatically decrypt vault files, here's a simple workaround:
When stdout is not a tty, ansible-vault view <file> prints cleartext to stdout without invoking a pager.
In combination with a 'pipe' lookup, this behavior can be used with a vault password file to feed into the copy module's 'content' option:
- name: "install host key"
copy: content="{{ lookup('pipe', 'ansible-vault view ' + src_key_file) }}"
dest={{ dest_key_file }}
You can also use local_action to temporairly decrypt your file as part of the playbook:
- name: "temporairly decrypt the twpol.enc"
sudo: False
local_action: shell ansible-vault view --vault-password-file {{ lookup('env', 'ANSIBLE_VAULT_PASS_FILE') }} ./roles/copykey/files/key.enc > ./roles/copykey/files/key.txt
- name: "copy the key to the target machine."
copy: src=key.txt dest=/tmp
- name: "remove decrypted key.txt file"
sudo: False
local_action: rm ./roles/copykey/files/key.txt
+1 for the copy: content= {{ private_ssl_key }}" method suggested by #utapyngo above.
If you are doing your key distribution as a role, rather than just in a playbook (and why not, since key distribution is something you might need again later), keep in mind the following:
You only get one file for your vars, so all the keys (say you have different bundles based on host machines or whatever) have to go together in <role>/vars/main.yml
Variables in this <role>/vars/main.yml are referenceable without any paths (that's nice!)
Remember that whenever you want the content of the variable, you need quotes AND curlies, i.e., "{{ your_variable_name }}"
If you want to copy more than one file per task, you need a with_items: loop
If you want to keep your sensitive data that you've gone through so much trouble to encrypt in the first place off the screen, a neat trick is to next your key variables inside a dictionary; that way, in your with_items loop you are feeding it the dictionary key rather than the contents of the variable itself.
Related
I have a playbook which calls a script file. It is going to pass a secret to the script file.
What I would like is for the secret not to be shown in standard out.
According to the ansible documentation, no_log: true can be used however this will not work when used in a ANSIBLE_DEBUG env variable is set true.
What I am also finding is that in the task "Set Execution File and parameters", the variables are being displayed even with no_log: true.
---
- block:
- name: Set Execution File and parameters
set_fact:
scriptfile: "{{ansible_user_dir}}\\scripts\\host_check.ps1"
params: " -servername '{{the_host_name}}' -secret {{my_secret}}"
- name: Execute script
win_command: powershell.exe "{{scriptfile}}" "{{params}}"
no_log: True
rescue:
- debug:
msg: "Play failed"
I would like to replace the my_secret variable with *******
Actually no_log: true is all you can do. Ansible will need to know your secret to use it and anybody who can set ANSIBLE_DEBUG can also just edit your playbook to display the secret. So as much as you hide it, it can always be revealed.
You have, however, the possibility to encrypt the secret using ansible-vault and pass the password to ansible, so nobody except you (or has the password) can use (and my this display) the secret.
This is how you encrypt a value (<secret-value> in this case):
ansible-vault encrypt_string --ask-vault-pass '<secret-value>' --name 'secret'
You will be asked for a password (I used test here) and then will get something that looks like this:
secret: !vault |
$ANSIBLE_VAULT;1.1;AES256
36323534396462626132653332653266393832306337336164373834626330373132363136633865
6161623130363364643138633236306334313833663535380a666261383838313631396264626534
66373637653937616231353361633635353238333630303563343630333161626137396332616263
3966356531613234370a633331333862616164386130663262613430316630373230373833313330
6265
You place that in your inventory like this (just an example what it should look like, you need to put in your hosts and other variables):
---
all:
hosts:
your-host:
vars:
secret: !vault |
$ANSIBLE_VAULT;1.1;AES256
36323534396462626132653332653266393832306337336164373834626330373132363136633865
6161623130363364643138633236306334313833663535380a666261383838313631396264626534
66373637653937616231353361633635353238333630303563343630333161626137396332616263
3966356531613234370a633331333862616164386130663262613430316630373230373833313330
6265
the_host_name: host.name
ansible_user_dir: 'C:\\some\\dir'
Then you can do this in your playbook:
---
- block:
- name: Execute script
win_command: 'powershell.exe "{{ ansible_user_dir }}\\scripts\\host_check.ps1" -servername "{{ the_host_name }}" -secret "{{ my_secret }}"'
no_log: true
rescue:
- debug:
msg: "Play failed"
You need to run it with --ask-vault-pass like this:
ansible-playbook -i your-inventory.yml your-playbook.yml --ask-vault-pass
It will ask you for the password every time you run it, so make sure to keep it somewhere.
If you are using multiple encrypted values in one run, you need to use the same password to encrypt all of them.
I would like to use Ansible to
Generate and encrypt an SSH key pair on the target
Add the SSH public key to GitHub
Clone a private GitHub repository
I explicitly do not want to forward any SSH keys from the control node to the target (never understood the point of doing this) or even worse, copy SSH keys from the control node to the target. I also don't want to keep the private key on the target in plain text. All answers to other questions I've seen always suggest one of these three ways, all of which are bad.
Instead, I want to generate an own pair of SSH keys on each target and of course encrypt the private key so that it doesn't lie around on the target in plain text.
I've so far not managed to do this. Can anyone help?
Here is what I've tried so far (assume all variables used exist):
- name: Generate target SSH key for accessing GitHub
command: "ssh-keygen -t ed25519 -f {{ github_ssh_key_path }} -N '{{ github_ssh_key_encryption_password }}'"
- name: Fetch local target SSH public key
command: "cat {{ github_ssh_key_path }}.pub"
register: ssh_pub_key
- name: Authorize target SSH public key with GitHub
github_key:
name: Access key for target "{{ target_serial }}"
pubkey: "{{ ssh_pub_key.stdout }}"
token: "{{ github_access_token }}"
- name: Clone private git repository
git:
repo: git#github.com:my_org/private_repo.git
clone: yes
dest: /path/to/private_repo
The problem with the encrypted key is that I then get a "permission denied" error from GitHub. I probably need to add the key to ssh-agent, but I haven't been able to figure out how. Or is there an alternative?
The above works fine if I do not encrypt the SSH private key, i.e. if I do
- name: Generate target SSH key for accessing GitHub
command: "ssh-keygen -t ed25519 -f {{ github_ssh_key_path }} -N ''"
instead of the command above, but of course I don't want that.
- name: Generate target SSH key for accessing GitHub
ansible.builtin.command:
cmd: "ssh-keygen -t ed25519 -f {{ github_ssh_key_path }} -N '{{ github_ssh_key_encryption_password }}'"
creates: "{{ github_ssh_key_path }}"
- name: Fetch local target SSH public key
ansible.builtin.slurp: "{{ github_ssh_key_path }}.pub"
register: ssh_pub_key
- name: Authorize target SSH public key with GitHub
github_deploy_key:
name: Access key for target "{{ target_serial }}"
key: "{{ ssh_pub_key.content }}"
owner: "{{ my_org }}"
read_only: true
repo: "{{ private_repo }}"
token: "{{ github_access_token }}"
- name: Clone private git repository
git:
repo: "git#github.com:{{ my_org }}/{{ private_repo }}.git"
clone: true
dest: "/path/to/{{ private_repo }}"
Use community.general.github_deploy_key
to instantiate a deploy key with read_only: true (default) so that the ssh key is tied only to the single GitHub repo. This way it can only be used to get (and not modify) the specific repo that you are cloning to the system anyways. If they can read the plain text private key on the host, they can read the contents of the repo locally. Technically, this does potentially expose more. Future versions of the repo (since the initial pull by the ansible playbook) could be accessed directly from GitHub. If the initial pull restricted what was pulled (e.g. depth: 1 or single-branch: true) more history or branch structure than exists on the local file system could be pulled with the key.
Use ansible.builtin.slurp to read the contents of the public key without resorting to command (better idempotence reporting).
Use creates parameter with ansible.builtin.command module to not recreate the key each time.
Use true/false to avoid Norway problem related ambiguity with yes/no
While this does not answer the tactical question, it may answer the strategic question, depending on the risk budget.
If the private key being encrypted is a hard requirement, it looks like shelling out to ssh-add after launching ssh-agent will be required, and highly host OS dependent for exact syntax details (e.g. how to give ssh-add the passphrase, as discussed here How to make ssh-add read passphrase from a file?). Further, the implementation details vary depending on the ongoing access you want the host to have with the key:
Should it only be usable when this ansible playbook unlocks the key, or should the passphrase be saved to a system keychain (highly host OS dependent)?
Should ssh-add implement a destination constraint (-h) when adding the keys to the agent?
Should the playbook add GitHub as a known host in the ssh config?
ansible_user ssh config, or system-wide?
public GitHub.com, or local instance?
Should ssh-agent start automatically (e.g. on reboot or login), or through manual/ansible intervention?
Do you even want to keep the key on server after completing the clone?
No decreases idempotency, but arguably increases security (or alternatively securty theater).
If not, you will also want to state: absent the github_deploy_key, too.
It does not look like there are (yet) ansible modules to manage the remote host ssh-agent state or keys. ssh agent forwarding seems to be widely accepted by the community and accomplishes most objectives (keeping the authorized key from being persistently stored on the remote host, only allowing use of the key while the agent is forwarded, etc.), so I expect no such module will be forthcoming.
I want to create user accounts with corresponding passwords all in a single playbook. To do this I define the users I need for my project and want to generate passwords for them. The attached script creates a password per user and assigns it to the r variable. This all works fine, I can call the items with their passwords. However I would like to save the usernames and passwords into a vault file that is encrypted all from within the playbook. But I can't figure out a way to do this.
Currently I write the output to the userfile.txt (output is a single line of .json, would like to have it in a nicer format) but then I would have to run an extra ansible-vault encrypt command. Is there a way to do this all from within a single playbook?
vars:
users:
- testuser1
- testuser2
tasks:
- name: Generate passwords
script: generate_passwords.py "{{item}}"
register: "r"
args:
executable: python3
with_items: "{{ users }}"
# The following proves that I can call the username and password:
- debug: msg="username={{item.item}}, password={{item.stdout_lines[0]}}"
with_items: "{{r.results}}"
# This saves everything in cleartext, how to do this into a vault file?
- copy:
content: "{{r}}"
dest: userfile.txt
I want to store my user's public keys in vault instead of a file.
How should i change my playbook:
name: push users public_keys
authorized_key:
state: present
user: admin
key: ""{{ lookup('file', '/path/to/your/www_id_rsa.pub') }}""
Thanks
One way could be storing your vault variables in a file or encrypt a string.
I'll show the use of file:
ansible-vault create vault_vars.yml (You'll be asked to create a password)
In the vault_vars.yml you create a normal variable containing the public_key: pub_key: "rsa..."
Include the file in your playbook:
vars_files:
- vault_vars.yml
You can access the variable from the vault just like you would with a variable being defined inside the playbook: {{ pub_key }}
An example printing the variable from the vault:
- hosts: server
vars_files:
- vault_vars.yml
tasks:
- debug: msg="{{pub_key}}"
Running playbook: ansible-playbook playbook.yml --ask-vault-pass
You can either use --ask-vault-pass (being prompted for password) or --vault-password-file (storing your vault password in a file)
EDIT
After reading the question again, and you stated you did not want to use a file meaning the solution would be as suggested below: Encrypt the string.
Personally I am not a big fan of encrypting strings and then put them directly in the playbook. I prefer to encrypt the file instead, making it easier to add/change values if needed.
I don't really get the point of encrypting a public key. You usually want to do this for private keys (or any other kind of sensitive data like passwords, tokens...).
Meanwhile, if you really want to do that, you don't need to change anything to your playbook. You just have to encrypt the file containing your public key:
ansible-vault encrypt --ask-vault-pass /path/to/your/www_id_rsa.pub
Once this is done, ansible will automagically detect this is a vault encryted file when you try to use it and it will decrypt it on the fly. For this to work, you will of course have to provide the same vault password when you execute the playbook (or you will get an error saying that the vault could not be decrypted).
ansible-playbook -i my_inventory --ask-vault-pass my_playbook.yml
For more info on different options to provide the vault pass/id see: https://docs.ansible.com/ansible/latest/user_guide/vault.html
I have a job in Rundeck, which require users to pass in database password to ansible. And ansible will take it as an extra variable.
ansible-playbook test.yml -e "password=123"
However, we would like to vault the password during the runtime, but from ansible's best practice. They would require the password to be stored in a file.
and vault the entire file using ansible-vault create.
Since we have a large number of the password to pass in, and I notice there is a function call encrypt_string. I try to call it in a playbook and try to generate a vault password on the fly, but I'm getting error below:
"ERROR! Only one --vault-id can be used for encryption. This includes
passwords from configuration and cli."
Here is my playbook test.yml:
---
- name: test
hosts: localhost
tasks:
- name: vault var
command: ansible-vault encrypt_string "{{ password }}" --vault-password-file ~/.vault_pass.txt
register: var
- name: variable
set_fact:
mypass: var
- name: test encrypt_string
debug:
msg: "{{ mypass }}"
I'm not sure if this is the correct way to do it/best practice, anyone can shed some light will be very appreciated.
Thanks,
You may update your task by removing option --vault-password-file as ansible seems getting/reading it from your environment some way.
...
...
- name: test
hosts: localhost
tasks:
- name: vault var
command: ansible-vault encrypt_string "{{ password }}"
register: var
...
...
If you prefer to keep this option in playbook, you may need to find where ansible is reading it from. Ansible may be reading it from it's default config file, generally found at ~/.ansible.cfg [look for vault_password_file] or alias or somewhere else.
You may find more details at ansible vault documentation with examples.