8.3 Vagrant SSH
Vagrant boxes set up SSH with a non-privileged user account vagrant. The vagrant account is configured for both password (also vagrant) access and a public/private key pair is preloaded to allow Vagrant SSH access to the server without the need for messy password integrations.
The upshot of this setup is that Vagrant controlled servers are inherently insecure by default as the username/password pair (vagrant/vagrant) is common knowledge.
The normal use-case for Vagrant renders this insecurity moot because we would not normally make these machines accessible outside our host computer and certainly not on a public network. The difference is significant to our current setup because as we secure our server we need to ensure access to the vagrant account via SSH so that Vagrant continues to work.
We could secure our Vagrant setup further by changing the vagrant account password, but frankly this is overkill for our purposes. Our Vagrant systems are intended only for use in development on individual host computers, which should themselves have host firewalls preventing unwanted network access to the Vagrant systems.
The most important thing we need to do though is isolate, so far as practicable, any Vagrant specific configuration such that it does not interfere adversely with our development. In other words, we want to avoid making assumptions that are only applicable in our development Vagrant environment. Since our users (developers) may be unaware of these issues, we need to isolate them as far as practicable as part of our configuration.
8.3.1 Vagrant provisioning
As our first step in configuration let’s do something simple. We will need the Git system installed on our new server so this is a good simple thing for us to install.
Modify your Vagrantfile, inserting the config.vm.provision instruction.
1# -*- mode: ruby -*- 2# vi: set ft=ruby : 3 4Vagrant.configure("2") do |config| 5 config.vm.box = "debian/bullseye64" 6 config.vm.box_version = "v11.20220912.1" 7 config.vm.provider "virtualbox" do |vb| 8 vb.memory=8192 9 vb.cpus=4 10 end 11 config.vm.provision "shell", inline: "apt-get install -y git" 12end
The new line (11) instructs Vagrant to use its SSH connection to run the shell command apt-get install -y git on the guest operating system (our new server).
By default Vagrant will run all such provisioning shell commands sudo, that is with elevated ‘super user’ privileges. (The vagrant account is an unprivileged account but is configured as a sudo user, see sudo users.)
Assuming you still have your Vagrant VM running from earlier, you can have vagrant ‘re-provision’ the VM rather than needing to destroy it and start over. On your host computer, while in the same directory as the Vagrantfile4.
1vagrant up --provision
By default (without the --provision option) vagrant up will start a halted or suspended Vagrant VM without running any provision entries. With the --provision option the VM is started (if halted or suspended) and any provision commands in the Vagrantfile are run.
In our example the inline shell command apt-get install -y git will be run and the Git package will be installed on the VM.
If the VM is already running, that is not halted or suspended, the provision entries in the Vagrantfile are still run against the running VM.
If the VM does not exist (it has not been previously created with vagrant up or it has been destroyed with vagrant destroy) then the VM will be created as normal and the provision entries will be run as part of the creation of the VM.
This simple approach to configuring our VM seem okay for simple things but has one major problem, it is not portable. Suppose we want to apply this configuration to another server, one not controlled by Vagrant. We would need to somehow extract all the config.vm.provision directives to apply the relevant configuration. Not very practical.
What we need to do is decouple the configuration actions from the Vagrant mechanism that invokes those actions.
8.3.2 Vagrant provision by script
Modify your Vagrantfile again, replacing the config.vm.provision directive.
1# -*- mode: ruby -*- 2# vi: set ft=ruby : 3 4Vagrant.configure("2") do |config| 5 config.vm.box = "debian/bullseye64" 6 config.vm.box_version = "v11.20220912.1" 7 config.vm.provider "virtualbox" do |vb| 8 vb.memory=8192 9 vb.cpus=4 10 end 11 config.vm.provision "shell", path: "scripts/configure"" 12end
We have added line 11 to copy a file (scripts/configure) from the host computer to the VM (into /tmp/vagrant-shell/configure) and then execute this script on the guest VM.
Next we need to create the configure script. On the host computer, in the directory containing the Vagrantfile.
1mkdir scripts 2vi scripts/configure
Use whatever your preferred text editor is (I’m using vi here).
Enter the following into the configure file.
1#!/usr/bin/env bash 2# vi :set ft=bash: 3 4set -euo pipefail 5 6apt-get install -y git
The first line ensures that Linux invokes the correct interpreter when none is specified. The second line is a ‘mode’ line telling vi that this is a bash script (not important if you do not use vi, but I do so adding this mode line is habit for me). Line 4 ensures the script fails early and hard if it has any errors (not strictly useful in such a short script, but a good habit to acquire).
Line 6 is the important line and reproduces the install of the Git package.
This may all seem rather overkill, and it is for such a trivial example, but it illustrates an important principal. Moving our configuration instructions into script means we can copy that script to any system we want to configure and run it. This configuration is now independent of Vagrant, relying solely on Linux script interpreters5. The only parts of our configuration process that are tied to Vagrant are the provision directive, these contain no configuration information other than which script to upload and run for this particular VM.