3

I'm using ansible in a vagrant environment managing a debian box. Due multiple ansible provisioners with vault encrypted data (e. g. database root password) I have to enter the vault password once by the first provisioner. Currently this password is stored in /tmp, read by script in every ansible provisioner and overwritten with /dev/null and then deleted by the last provisioner.

Ansible is able to call a script which returns the vault password. So this script will run in a separate shell.

If an attacker managed to break into the VM he has the opportunity to recover any temporary file and to get in possesion of the vault password.

While these vagrant environments will be used in production I'm looking for a more secure approach. What first came into my mind is to read and write into memory somehow. So a VM reboot would wipe the memory. I know the mentioned data could somehow be swapped on disk. But I think it's more difficult to get this data than the temporary file approach.

Edit

I forgot to mention the security risk if a provisioner fails. Then the last provisioner will not be executed and the temporary file remains on the file system.


I provided an additional answere showing the integrated solution just for anyone interested in the solution of the origin problem that led to this question.

  • 1
    I'll take a look this afternoon. – codekandis Mar 12 at 13:06
  • Hmmm ... sounds very good. As far as I researched /etc/fstab can be changed and mount -a executed so I don't need to restart the system during the provisioning - that's an important pro. Both RAM disks would be used temporarily for some minutes during the provisioning. But yes, due to any issues during the provisioning any ramfs mounts could last and it's not limitable. While I'd limit the size of the used tmpfs RAM to 1 MB tmpfs shouldn't swap anything. So I think it's a very good and stable solution. Make your comment an answere and I'll check it! – codekandis Mar 12 at 13:53
1

To create a 1 MB RAM disk using tmpfs:

mkdir /tmp/ramdisk
mount -t tmpfs -o size=1m myramdisk /tmp/ramdisk

The ramfs RAM disk does not swap, yet swapping can be turned off and on while using the tmpfs RAM disk: swapoff -a and swapon -a, respectively, if a need to assure that no swapping has occurred during the use of the tmpfs RAM disk. To unmount:

umount /tmp/ramdisk

Maybe I missed something, but I do not see the benefit of describing the RAM disk in /etc/fstab; but, if it is needed, something such as the following should suffice to cause the RAM disk to be available to the root user only (mode=0700):

tmpfs /tmp/ramdisk tmpfs rw,mode=0700,size=1m
  • Very cool. Thank you. I didn't know I can use mount for tmpfs. AFAIK I cannot turn off swapping for a sepecific tmpfs only. I only can turn off one or all swap(s) with swapoff. During the provisioning of a running system I won't turn off swapping to prevent any side effects. This is the reason to set the tmpfs to 1 MB while the password file is less then 1 MB. At least 512 B fit. If the RAM is already full I should have monitored and handled it earlier. This is surely vulnerable if the system is already compromised but it will restrict the attacker's possibilities. – codekandis Mar 12 at 17:39
1

In addition to @Christopher's answere I like to show how I integrated the solution into my Vagrantfile in order to get it working with my ansible povisioning.

May one stumble upon this.

Vagrantfile

Vagrant.configure("2") do |config|

    class AnsibleVaultPassword
        def to_s
            begin
                system 'stty -echo'
                print "Enter ansible vault password: "
                pass = STDIN.gets.chomp
                ensure
                system 'stty echo'
            end
            pass
        end
    end

    config.vm.box = "vendor/box-name"
    config.vm.box_version = ">=1.0"
    config.vm.box_url = "https://vagrant.example.com/vendor/box-name.json"
    config.vm.box_download_insecure = true

    config.vm.define "vendor-server-name"
    config.vm.provider "virtualbox" do |provider|
        provider.name = "vendor-server-name"
    end

    config.vm.network "private_network", ip: "192.168.0.42"

    config.vm.synced_folder ".", "/vagrant", type: "nfs"

    config.vm.provision "Provisioning the server `server-name` with the playbook `ansible`", type: "ansible_local" do |provisioner|
        provisioner.compatibility_mode = "2.0"
        provisioner.playbook = "/vagrant/ansible/ansible.yml"
        provisioner.inventory_path = "/vagrant/ansible/inv/integration/hosts"
        provisioner.limit = "localhost"
    end

    config.vm.provision "Requesting the ansible vault password", type: "shell" do |provisioner|
        provisioner.env = { "ansibleVaultPassword" => AnsibleVaultPassword.new }
        provisioner.inline = <<-END
            [[ ! -d "/mnt/ansible-tmp" ]] \
            && mkdir "/mnt/ansible-tmp"
            mountpoint -q "/mnt/ansible-tmp" \
            || mount -t tmpfs -o size=512 ansible-tmp "/mnt/ansible-tmp"
            /vagrant/env/ansible/scripts/vault-password.sh --save "${ansibleVaultPassword}"
        END
    end

    config.vm.provision "Provisioning the server `server-name` with the playbook `environment`", type: "ansible_local" do |provisioner|
        provisioner.compatibility_mode = "2.0"
        provisioner.playbook = "/vagrant/ansible/environment.yml"
        provisioner.raw_arguments = [ "--vault-id /vagrant/ansible/scripts/vault-password.sh" ]
        provisioner.inventory_path = "/vagrant/ansible/inv/integration/hosts"
        provisioner.limit = "localhost"
    end

    config.vm.provision "Provisioning the server `server-name` with the playbook `users`", type: "ansible_local" do |provisioner|
        provisioner.compatibility_mode = "2.0"
        provisioner.playbook = "/vagrant/ansible/users.yml"
        provisioner.raw_arguments = [ "--vault-id /vagrant/ansible/scripts/vault-password.sh" ]
        provisioner.inventory_path = "/vagrant/ansible/inv/integration/hosts"
        provisioner.limit = "localhost"
    end

    config.vm.provision "Deleting the ansible vault password", type: "shell" do |provisioner|
        provisioner.inline = <<-END
            /vagrant/env/ansible/scripts/vault-password.sh --delete
            mountpoint -q "/mnt/ansible-tmp" \
            && umount "/mnt/ansible-tmp"
            [[ -d "/mnt/ansible-tmp" ]] \
            && rm -r "/mnt/ansible-tmp"
        END
    end

end

/vagrant/ansible/scripts/vault-password.sh

#!/usr/bin/env bash

passwordFile="/mnt/ansible-tmp/vault-password"
case "${1}" in
    "-s" | "--save" )
        echo "${2}" > "${passwordFile}"
        ;;
    "-d" | "--delete" )
        unlink "${passwordFile}"
        ;;
    * )
        cat "${passwordFile}"
        ;;
esac

While it's a Debian VM I first run the playbook ansible which adds the ansible repository from Ubuntu and then installs the latest ansible. This is at least necessary to provide the argument --vault-id to the ansible-playbook call of Vagrant. (At the time of this post Debian Stretch comes with ansible 2.2 which doesn't support that argument.)

Then the vault password is requested with the class AnsibleVaultPassword. The tmpfs will be created afterwards and the password will be passed to the script vault-password.sh to store it on the tmpfs mount.

The password will then be requested by ansible via the script vault-password.sh in every playbook providing vault encrypted data.

And at the end the password will be deleted first, then the tmpfs mount will be unmounted.

I think this is a very safe and secure solution to provide the ansible vault password via a temporary wipeable memory storage. So a running system can be provisioned and a reboot isn't necessary anymore.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.