Introduction

Secure ROS is a “fork” of core ROS packages to enable secure communication among ROS nodes (not to be confused with SROS, http://wiki.ros.org/SROS/). The main goal of Secure ROS is to enable secure communication for regular users of ROS.

The modified packages are rosmaster, rosgraph, roscpp, rospy, xmlrpcpp and nodelet.

Secure ROS uses IPSec in transport mode and modified versions of the ROS master, rospy module and roscpp library to ensure secure communication. At run time, the user can specify authorized subscribers and publishers to topics, setters and getters to parameters and providers (servers) and requesters (clients) of services in the form of a YAML configuration file for the ROS master. Secure ROS allows only authorized nodes to connect to topics, services and parameters listed in the configuration file.

Secure ROS keeps ROS public API intact, allowing user level ROS packages to be used without modification. If no YAML configuration is provided, Secure ROS behaves like regular ROS (with no security).

The Secure ROS website is at http://secure-ros.csl.sri.com/.

Installation

Please install ROS first (see http://wiki.ros.org/ROS/Installation).

To install Secure ROS, download the debian package from http://secure-ros.csl.sri.com/download/ and install with dpkg. E.g.

dpkg -i secure-ros-<RELEASE>-secure-ros_<VERSION>_<ARCH>.deb

The Secure ROS package files are installed in /opt/secure_ros/<release>/ and includes modified versions of some ROS libraries and modules.

Usage

The user can use the features of Secure ROS by sourcing /opt/secure_ros/<release>/setup.bash instead of /opt/ros/<release>/setup.bash. This will automatically include all packages installed in /opt/ros/<release>/.

To enable secure communication, you also need to provide an authorization configuration file (relative to the working directory) when starting the ROS master. E.g.

ROS_AUTH_FILE=ros_auth.yaml roscore

If the ROS_AUTH_FILE environment variable is not defined, the Secure ROS master behaves like the regular ROS master. The configuration file is described in the next section. For further details, please see the example.

Authorization Configuration

A sample configuration file from the multi_pubsub example is provided below. The authorization configuration YAML file is a dictionary with the following keys: aliases, topics, nodes, parameters and services. The topics key is required but the others are optional. These key-value pairs are described in detail below.

aliases:
  vm2: [192.168.10.202]
  vm3: [192.168.10.203]
topics:
  /chatter:
    publishers: [vm2]
    subscribers: [vm3]
  /counter:
    publishers: [vm2]
    subscribers: [vm3]
nodes:
  /talker: [vm2]
  /listener: [vm3]
  • aliases: The value of the aliases key is a dict of (key,value) pairs where each keys is an alias and each value is the corresponding list of IP addresses. The IP address can also be a hostname that can be resolved by the master.

  • topics: The value of the topics key is a dict of (key,value) pairs where the keys are all the allowed topics excepting reserved topics. The reserved topics are rosout and rosout_agg. Each topic value is another dictionary with two required keys: publishers and subscribers. The value of the publisher and subscriber keys is a list of IP addresses that are the authorized publishers and subscribers to that topic respectively.

  • nodes: This is an optional key. The value of the key is a dict of (key,value) pairs where the keys are node names excepting reserved nodes. The reserved nodes are rosout. Each value is list of the IP addresses from which that node can register. If a node is listed as a key, then that node may only register from one of the associated IP addresses. If a node is not listed as a key or if the nodes key is not present, then any node may register from the list of authorized_ip_addresses.

  • parameters: This is an optional key. The value of the key is a dict of (key,value) pairs where the keys are parameter names (or parameter prefixes) other than the reserved parameters. The value of each key is another dict with two keys: setters (required) and getters (optional). The value of the setters and getters keys is a list of IP addresses that are the authorized setters and getters of that parameter respectively. If a parameter name is not listed, then that parameter may be set and accessed from any IP address in authorized_ip_addresses. If a parameter does not have the getters key, that parameter may be accessed from any IP address in authorized_ip_addresses.

    The reserved parameters are: /run_id, /rosversion, /rosdistro, /tcp_keepalive, /use_sim_time, /enable_statistics, /statistics_window_min_elements, /statistics_window_max_elements, /statistics_window_min_size, /statistics_window_max_size

  • services: This is an optional key. The value of the key is a dict of (key,value) pairs. The keys are service names other than the reserved services. The value of each key is another dict with two keys: providers (required) and requesters (optional). The value of the providers and requesters keys is a list of IP addresses that are the authorized providers and requesters of that service respectively. If a service name is not listed, then that service may be set and accessed from any IP address in authorized_ip_addresses. If a service does not have the requesters key, that service may be accessed from any IP address in authorized_ip_addresses.

authorized_ip_addresses is an internal set containing all the IP addresses that are listed in the authorization file. Any request from an IP address not on this list is, in general, discarded. This list is also used as an authorized list for nodes, parameters or services that are not explicitly listed in the file.

IPSec configuration

Secure ROS relies on IPSec to ensure that IP packets are not tampered or spoofed. Therefore, IPsec needs to be configured correctly in order for Secure ROS to work well against malicious attacks. We have provided the secure_ros_tools python package in order to generate keys and configuration files for IPSec. The following steps need to be followed to create the IPsec configuration files.

  • Install the secure_ros_tools package.

    sudo apt-get install racoon ipsec-tools python-pip python3-pip
    sudo pip install git+https://github.com/SRI-CSL/secure_ros_tools.git
    
  • Create YAML file (e.g. hosts.yaml) with all hosts in the system as follows. In this example, there are three hosts.

    machine1: 192.168.10.201
    machine2: 192.168.10.202
    machine3: 192.168.10.203
    
  • Generate configuration files for IPsec.:

    create_ipsec_conf -i hosts.yaml
    

    This will create a folder for each machine with keys and configuration files. E.g. the files for machine1 are located in output/machine1. A tar.gz file is also created for each machine.

  • Copy the tar.gz files to the appropriate machines and untar them into the appropriate folder on the respective machines.

    E.g., on machine1, untar the contents of machine1.tgz.

    sudo tar xzf machine1.tgz -C /
    

Examples

Simple talker listener example (simple_pubsub)

This example is a simple example to run Secure ROS using the built-in talker and listener example in a linux machine. The ROS master and nodes run on the same machine.

Requirements

Install ROS and corresponding version of Secure ROS on Ubuntu (ROS Indigo on 14.04 and ROS Kinetic on 16.04).

Using Secure ROS

You need to source Secure ROS in each terminal.

source /opt/secure_ros/kinetic/setup.bash

Authorization configuration

The authorization file (ros_auth_simple_pubsub.yaml) is shown below. It allows certain nodes and all topics to be published to and subscribed to from your machine (machine1, IP address: 127.0.0.1).

aliases:
  machine1: [127.0.0.1]
topics:
  /chatter:
    publishers: [machine1]
    subscribers: [machine1]
  /counter:
    publishers: [machine1]
    subscribers: [machine1]
nodes:
  /talker: [machine1]
  /listener: [machine1]

Running the example

All the nodes are run on the same machine (machine1). Change the working directory to examples/simple_pubsub in all the cases and start the master and nodes in separate consoles. The ROS_AUTH_FILE path is resolved with respect to the current working directory. Note that if ROS_IP is not set, the ROS master is bound to all the network devices. ROS_MASTER_URI has the default value http://<hostname>:11311 which resolves to 127.0.0.1 in this case.

unset ROS_IP
unset ROS_MASTER_URI
source /opt/secure_ros/kinetic/setup.bash
  • Start the master node from the simple_pubsub directory.

    ROS_AUTH_FILE=ros_auth_simple_pubsub.yaml roscore
    
  • Start the talker node.

    rosrun test_secure_ros talker.py
    

    You may also run the C++ version instead of the python version (rosrun test_secure_ros talker2)

  • Start the listener node.

    rosrun test_secure_ros listener.py
    

    You may also run the C++ version instead of the python version (rosrun test_secure_ros listener2)

Test Secure ROS

We test Secure ROS by querying the master locally and externally as described in the following sections.

Test locally

You need to source Secure ROS in each terminal. ROS_MASTER_URI has the default value which is http://<hostname>:11311 and the requests are routed through the local network.

unset ROS_IP
unset ROS_MASTER_URI
source /opt/secure_ros/kinetic/setup.bash

Run the commands in ROS commands. You should have full access.

Test externally

If you set ROS_MASTER_URI with the external IP address of your machine (e.g. http://192.168.10.201:11311), then the requests are routed through the network and not loopback (127.0.0.1).

You will need to replace the IP address 192.168.10.201 with your actual IP address.

source /opt/secure_ros/kinetic/setup.bash
export ROS_IP=192.168.10.201
export ROS_MASTER_URI=http://192.168.10.201:11311

Run the commands in ROS commands. Secure ROS will deny the requests since the external IP address (192.168.10.201) is not an authorized IP address for any of these topics.

You may repeat this example with ros_auth_simple_pubsub2.yaml where the external IP address has been added to the alias for machine1. (You will need to replace the IP address 192.168.10.201 in ros_auth_simple_pubsub2.yaml with your actual IP address.) In this case, irrespective of whether you use the local (http://localhost:11311) or external IP address (http://192.168.10.201:11311) in the ROS_MASTER_URI on machine1, you will be able to gain full access.

However, you will not be able to access the nodes from another machine (machine2, IP address e.g.: 192.168.10.202).

On machine2 (or any other machine on the network), set ROS_MASTER_URI.

source /opt/secure_ros/kinetic/setup.bash
export ROS_MASTER_URI=http://192.168.10.201:11311/

You will also need to set ROS_IP if the hostname of machine2 cannot be resolved from machine1. Please see ROS Network set-up for details.

export ROS_IP=192.168.10.202

You may then run the commands in ROS commands. You should be unable to register with the master, subscribe, publish, or otherwise access information.

Test from network

You may modify the previous example by adding a second authorized machine (e.g. machine2 with IP address 192.168.10.202) to the subscribers for topic /chatter (ros_auth_simple_pubsub_network.yaml). Run the following commands in each terminal in machine1. You only have to set ROS_IP on all machines if all the hostnames cannot be resolved by all the machines as is standard ROS Network set-up.

source /opt/secure_ros/kinetic/setup.bash
export ROS_MASTER_URI=http://192.168.10.201:11311
export ROS_IP=192.168.10.201

To start the master node on machine1,

ROS_AUTH_FILE=ros_auth_simple_pubsub2.yaml roscore

Start the talker on machine1,

rosrun test_secure_ros talker.py

Start the listener on machine1,

rosrun test_secure_ros listener.py

On machine2, set ROS_MASTER_URI.

source /opt/secure_ros/kinetic/setup.bash
export ROS_MASTER_URI=http://192.168.10.201:11311/

You will also need to set ROS_IP if the hostname of machine2 cannot be resolved from machine1.

export ROS_IP=192.168.10.202

You should then be able to subscribe to /chatter from machine2.

rostopic echo /chatter

However you will be unable to subscribe to /counter from machine2.

rostopic echo /chatter

You will also have restricted access to information from the master on machine2. E.g. rostopic list will list only a subset of the topics (i.e. the topics that machine2 needs to know).

ROS commands

You may test Secure ROS by trying ROS command line tools.

  • Query rosmaster about topics

    rostopic list
    
  • Subscribe to topics

    rosrun test_secure_ros listener.py --anon
    
  • Publish to topics

    rosrun test_secure_ros talker.py --anon
    
  • Call service (e.g. /talker/get_loggers)

    rosservice call /talker/get_loggers
    
  • Get parameters

    rosparam list
    rosparam get /rosdistro
    
  • Set parameters

    rosparam set /rosdistro "jade"
    rosparam get /rosdistro
    
  • Kill nodes

    rosnode kill -a
    

Multi-machine talker listener example (multi_pubsub)

This example is a multi-machine example to run Secure ROS using the built-in talker and listener example. This example uses multiple VMs configured to use IPSec and requires Virtualbox, Vagrant and Ansible. Please see the section on Vagrant and ansible for details on installing and using vagrant and ansible.

The vagrant configuration for this example is loaded from the example.yaml file

cpus: 1
memory: 1024
master_ip_address: 192.168.10.201
arch: amd64
pkg: ros-comm
machines:
  pubsub1: 192.168.10.201
  pubsub2: 192.168.10.202
  pubsub3: 192.168.10.203
  pubsub4: 192.168.10.204

The minimum number of Virtual Machines (VMs) required for the example is 3. pubsub4 may be used as a malicious machine to test Secure ROS and intercept ROS communication.

Requirements

The following requirements are needed to run the example.

  • Connection to the internet on the host machine so that vagrant and ansible can fetch the images and debian packages.
  • At least N=4 free cores on the host machine
  • At least 4096MB RAM for the guest machines (1024 MB per guest)

If you don’t meet the requirements for N=4, you may try with N=3 (3 cores, 3072MB RAM) or use less RAM.

Starting the VMs

You may bring up the VMs using the following command.

vagrant up

Vagrant tasks

  • Bring up the corresponding VMs as specified in the example.yaml configuration file.
  • Set up the networking as a private network along with appropriate IP addresses as specified in example.yaml.
  • Provision the VMs as specified in the example.yaml and secure_ros.yaml files.

Ansible playbooks

  • plays/install-secure-ros.yaml: This installs the appropriate Secure ROS package from a deb file. If the deb file is not present, it is is created once (and used from that point).
  • plays/build-secure-ros.yaml: This builds the appropriate Secure ROS package and creates the deb file.
  • plays/configure-secure-ros.yaml: This sets up the Secure ROS environment with the appropriate bashrc files containing ROS_IP, ROS_MASTER_URI, location of the source file.
  • plays/configure-racoon.yaml: This configures the system security (IPsec) using racoon. The secure keys are created in each VM and the public keys are distributed to all VMs. The racoon and setkey configuration files are also created and the corresponding services are started. This information

Authorization configuration

The authorization configuration is in ros_auth_multi_pubsub.yaml and its contents are presented below.

aliases:
  vm2: [192.168.10.202]
  vm3: [192.168.10.203]
topics:
  /chatter:
    publishers: [vm2]
    subscribers: [vm3]
  /counter:
    publishers: [vm2]
    subscribers: [vm3]
nodes:
  /talker: [vm2]
  /listener: [vm3]

Running the example

To run the nodes, run the following commands in the different VMs. Note that the correct environment is sourced from the files generated by ansible.

You will be able to ssh into each VM using vagrant from the root of the example folder (where the Vagrantfile is). For instance, to ssh into VM1, use the following command.:

vagrant ssh pubsub1

On VM1

ROS_AUTH_FILE=/vagrant/ros_auth_multi_pubsub.yaml roscore

On VM2

rosrun test_secure_ros talker.py

You may also run the C++ version instead of the python version.

rosrun test_secure_ros talker2

On VM3

rosrun test_secure_ros listener.py

You may also run the C++ version instead of the python version.

rosrun test_secure_ros listener2

Test example

You may test Secure ROS as follows.

  • Check IPSec: You may check the communication between VMs using tcpdump. Note that in Ubuntu 16.04, the network device may be named enp0s8 or similar rather than eth1. You may find the name of the device using ifconfig and checking the device name that has the IP address 192.168.10.201 on the guest machine.:

    vagrant ssh pubsub2
    sudo tcpdump -i eth1
    

    You should see something like the following. The IP packets exchanged between pubsub2 and pubsub3 (192.168.10.203) are shown with AH (Authentication Headers) and ESP (Encapsulating Security Payload).

    [vagrant@pubsub2 ~]$ sudo tcpdump -i eth1
    tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
    listening on eth1, link-type EN10MB (Ethernet), capture size 65535 bytes
    23:24:01.021656 IP pubsub2 > 192.168.10.203: AH(spi=0x0622f190,seq=0x4e): ESP(spi=0x0914f871,seq=0x4e), length 76
    23:24:01.022429 IP 192.168.10.203 > pubsub2: AH(spi=0x076e48d8,seq=0x50): ESP(spi=0x0c784394,seq=0x50), length 68
    23:24:02.021656 IP pubsub2 > 192.168.10.203: AH(spi=0x0622f190,seq=0x4f): ESP(spi=0x0914f871,seq=0x4f), length 100
    23:24:02.022335 IP 192.168.10.203 > pubsub2: AH(spi=0x076e48d8,seq=0x51): ESP(spi=0x0c784394,seq=0x51), length 68
    
  • Query rosmaster about topics: You may query the rosmaster for topics from VM1. All topics are shown.

    [vagrant@pubsub1 ~]$ rostopic list
    /chatter
    /counter
    /rosout
    /rosout_agg
    

    You may query the rosmaster for topics from VM2. Only topics relevant to VM2 are shown.

    [vagrant@pubsub2 ~]$ rostopic list
    /chatter
    /counter
    /rosout
    

    You may query the rosmaster for topics from VM4. Zero topics are shown since VM4 is not an authorized IP machine.

    [vagrant@pubsub4 ~]$ rostopic list
    
  • Starting a node from the wrong machine: Starting a new node with the same name as an existing node causes the master to send a shutdown signal to the existing node, but this is not allowed if the list of nodes with corresponding IP addresses is present in the authorization file as it is in the example.

    E.g. Launch /listener from VM3, but with the node name /talker. The node has permission to listen to the topics, but not to have the name /talker.

    [vagrant@pubsub3 ~]$ rosrun test_secure_ros listener.py --name talker
    [FATAL] [WallTime: 1490114153.855925] unable to register publication [/rosout] with master: Not authorized
    [FATAL] [WallTime: 1490114153.863927] unable to register service [/talker/get_loggers] with master: Not authorized
    [FATAL] [WallTime: 1490114153.867944] unable to register service [/talker/set_logger_level] with master: Not authorized
    Started node /talker
    Subscribing to /chatter
    [FATAL] [WallTime: 1490114154.377988] unable to register subscription [/chatter] with master: Not authorized
    Subscribing to /counter
    [FATAL] [WallTime: 1490114154.886248] unable to register subscription [/counter] with master: Not authorized
    

    On the master, you see that the node name registration is denied.

    [auth][WARNING] registerPublisher( /talker, /rosout, http://192.168.10.203:48231/, 192.168.10.203 ) caller_id not authorized
    [auth][WARNING] registerSubscriber( /talker, /counter, http://192.168.10.203:48231/, 192.168.10.203 ) caller_id not authorized
    
  • Subscribing to topics: You can try to subscribe to a topic from an unauthorized machine. Note that if an IP address is an authorized publisher to a topic, then it is implicitly an authorized subscriber.

    E.g. Run listener.py from VM2 with anonymous name.

    [vagrant@pubsub4 ~]$ rosrun test_secure_ros listener.py --anon
    FATAL] [WallTime: 1490826202.717182] unable to register publication [/rosout] with master: caller_id not authorized
    [FATAL] [WallTime: 1490826202.723819] unable to register service [/listener_11108_1490826202704/get_loggers] with master: caller_id not authorized
    [FATAL] [WallTime: 1490826202.727966] unable to register service [/listener_11108_1490826202704/set_logger_level] with master: caller_id not authorized
    Started node /listener_11108_1490826202704
    Subscribing to /chatter
    [FATAL] [WallTime: 1490826203.239365] unable to register subscription [/chatter] with master: caller_id not authorized
    Subscribing to /counter
    [FATAL] [WallTime: 1490826203.750798] unable to register subscription [/counter] with master: caller_id not authorized
    
  • Publishing to topics:

    You can try to publish to a topic from an unauthorized machine. E.g. Run talker.py from VM3 with anonymous name.

    vagrant@pubsub3 ~]$ rosrun test_secure_ros talker.py --anon
    ...
    Started node /talker_11475_1490827477247
    Publishing to /chatter
    [FATAL] [WallTime: 1490827477.878853] unable to register publication [/chatter] with master: topic not authorized
    Publishing to /counter
    [FATAL] [WallTime: 1490827478.385780] unable to register publication [/counter] with master: topic not authorized
    

    On the master.

    [auth][WARNING] registerPublisher( /talker_11475_1490827477247, /chatter, http://192.168.10.203:37924/, 192.168.10.203 ) topic not authorized
    ...
    [auth][WARNING] registerPublisher( /talker_11475_1490827477247, /counter, http://192.168.10.203:37924/, 192.168.10.203 ) topic not authorized
    
  • Service client: You can try to call a service from an unauthorized machine (VM4). E.g. Call /talker/get_loggers from VM4.

    [vagrant@pubsub4 ~]$ rosservice call /talker/get_loggers
    ERROR: Unable to determine type of service [/talker/get_loggers].
    

    On the master.

    [auth][WARNING] lookupService( /rosservice, /talker/get_loggers, 192.168.10.204 ) service not authorized
    
  • Get parameters: You can try to get parameter from unauthorized machine (VM4).

    [vagrant@pubsub4 ~]$ rosparam list
    

    You can try to get parameter from authorized machine (VM2).

    [vagrant@pubsub2 ~]$ rosparam get /rosdistro
    'indigo
    
      '
    
  • Set parameters: You can try to set parameter from unauthorized machine (VM2).

    [vagrant@pubsub2 ~]$ rosparam set /rosdistro "jade"
    ...
    rosgraph.masterapi.MasterError: parameter not authorized
    

    You can try to set parameter from authorized machine (VM1).

    [vagrant@pubsub1 ~]$ rosparam get /rosdistro
    'indigo
    
      '
    
    [vagrant@pubsub1 ~]$ rosparam set /rosdistro jade
    [vagrant@pubsub1 ~]$ rosparam get /rosdistro
    jade
    
  • Killing nodes: You can try to kill nodes from an unauthorized machine (only the master machine is allowed to kill nodes). First from VM3.

    [vagrant@pubsub3 ~]$ rosnode kill -a
    killing:
     * /listener
     * /rosout
     * /talker
    ERROR: Failed to kill:
     * /listener
     * /talker
    [vagrant@pubsub3 ~]$ rosnode list
    /listener
    /rosout
    /talker
    

    Next from VM1.

    [vagrant@pubsub1 ~]$ rosnode kill -a
    killing:
     * /listener
     * /rosout
     * /talker
    killed
    [vagrant@pubsub1 ~]$ rosnode list
    /rosout
    

Packaging Secure ROS

This folder contains tools (vagrant and ansible) for creating Ubuntu debian packages for Secure ROS. Please see the section on Vagrant and ansible for details on installing and using vagrant and ansible.

Supported architectures and packages

The following architectures are supported.

  • amd64 (64-bit PC)
  • i386 (32-bit PC)

The following releases are supported

  • ROS indigo (Ubuntu 14.04) on the indigo-devel branch.
  • ROS kinetic (Ubuntu 16.04) on the kinetic-devel branch.

The secure-ros-<release>-ros-comm package is created for each of the architectures. This includes modified versions of libraries (e.g libroscpp.so) and python modules (roslaunch, rospy) which will be installed in /opt/secure_ros/<release>/.

Generating the packages

The packages can be generated as follows.

vagrant up

The package is created in the root folder of the repository: deb/<arch>/secure-ros-<release>-ros-comm_<version>_<arch>.deb. The build is preserved if the VMs are halted, but will need to be rebuilt if the VMs are destroyed.

Vagrant tasks

  • Bring up the VMs as specified in the secure_ros.yaml configuration file.
  • Provision the VMs as specified in the secure_ros.yaml files (build, create and install the Secure ROS package).

Ansible playbooks

Vagrant and ansible

We use ansible and vagrant to create and configure multiple VMs to run the examples and create debian package for Secure ROS.

Install Vagrant and ansible

We support Ubuntu (14.04, 16.04) and OS X. It is relatively easy to install on Ubuntu, while on Mac OS X, it is a bit more involved.

Ubuntu

Install virtualbox:

sudo apt-get install -y virtualbox

Install vagrant:

wget https://releases.hashicorp.com/vagrant/1.8.5/vagrant_1.8.5_x86_64.deb
sudo dpkg -i vagrant_1.8.5_x86_64.deb

Install ansible:

sudo apt-add-repository ppa:ansible/ansible -y
sudo apt-get update
sudo apt-get install ansible -y

OS X

Install virtualbox from https://www.virtualbox.org/wiki/Downloads.

Install vagrant from https://releases.hashicorp.com/vagrant.

Install ansible (this is a bit more involved. The instructions from https://valdhaus.co/writings/ansible-mac-osx/ are provided here for convenience. If you already have Xcode and pip installed, you can skip those respective steps.

  • Install Xcode

  • Install pip

    sudo easy_install pip
    
  • Install ansible using pip

    sudo pip install ansible --quiet
    

Start up vagrant

Vagrant may be used to set up the VMs using two steps. These commands must be called from the example or package folders that contains Vagrantfile.

To fire up and configure the VMs using vagrant

vagrant up

Trouble-shooting

The most common issues are as follows.

  • Make sure you have an internet connection.

  • Make sure that you have as many cores and RAM available as needed in the example.

  • There might be a bug in ansible that may cause it to fail. You may try again as follows.

    vagrant provision
    
  • If it fails at the same spot, you may restart the VMs and try again.

    vagrant halt
    vagrant up --no-provision
    vagrant provision
    

Indices and tables