diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 205bedaf0b86d05a8f87ffcf368d6024d2fd57cb..b91f7d5f175a32a49a5ed0788a87146d9b86c3f3 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -23,6 +23,7 @@ unit-tests:
 vulnerability-scan:
   stage: "test"
   script:
+    - "sed -i 's/undefined/\"unundefined\"/' morph/grid/local/secrets/users.nix"
     - "ci-tools/vulnerability-scan security-report.json"
     - "ci-tools/count-vulnerabilities <security-report.json"
   artifacts:
diff --git a/ci-tools/vulnerability-scan b/ci-tools/vulnerability-scan
index 48bf51e071a398f37565717a22b2066d3f905fbe..3162e49511697ed0ea13e0121a67336405ce5225 100755
--- a/ci-tools/vulnerability-scan
+++ b/ci-tools/vulnerability-scan
@@ -21,7 +21,7 @@ OUTPUT=$1
 [ -e scan-target ] && rm -v scan-target
 nix-shell --run '
 set -x
-if morph_result=$(morph build morph/grid/testing/grid.nix 2>&1); then
+if morph_result=$(morph build morph/grid/local/grid.nix 2>&1); then
   object=$(echo "$morph_result" | tail -n 1)
   ln -s "$object" scan-target
 else
diff --git a/morph/README.rst b/morph/README.rst
index d4a89a373a1bef767ad26859d495f1528a4fb7ca..12472518ad8e061764d6812694c306e87553c843 100644
--- a/morph/README.rst
+++ b/morph/README.rst
@@ -42,6 +42,23 @@ grid
 
 Specific grid definitions live in subdirectories beneath this directory.
 
+secrets
+~~~~~~~
+
+This must be created and populated before the grid can be built or deployed.
+
+This directory contains all of the secrets necessary to deploy the grid.
+Secrets beneath this directory are referenced by ``config.json`` and ``grid.nix``
+(and possibly elsewhere).
+Some of the paths are configurable and some are just convention.
+This path is **ignored** by git.
+The intended workflow is that the secrets will be maintained on secure storage and a symlink to the correct location created here.
+This keeps the secrets themselves out of the git working tree as an extra protection against unintentionally committing them.
+
+An exception is the ``secrets`` directory in the ``local`` morph grid:
+That directory is fully populated, provided as an example, and mostly: not very secret.
+Do not deploy these keys to machines reachable via the internet.
+
 config.json
 ~~~~~~~~~~~
 
diff --git a/morph/grid/local/.gitignore b/morph/grid/local/.gitignore
index 86a37fb6544e909b2b2cd8579d5756d6d2d319da..8000dd9db47c0b9dd34046ec17880dcbb27e5eb9 100644
--- a/morph/grid/local/.gitignore
+++ b/morph/grid/local/.gitignore
@@ -1,2 +1 @@
 .vagrant
-
diff --git a/morph/grid/local/README.rst b/morph/grid/local/README.rst
index 38981f75bfb82dbac31269f8abdcd086ca7e4c8d..73bfbbdd2a11922fe696161fe8346d7e10157313 100644
--- a/morph/grid/local/README.rst
+++ b/morph/grid/local/README.rst
@@ -1,71 +1,51 @@
 Set up and use a network of local development VMs
 -------------------------------------------------
 
-... using `Vagrant <https://www.vagrantup.com/>`_ to manage VirtualBox VMs [#]_.
-To get started, first install Vagrant and make sure it works.
-One possible way to do it in NixOS:
+... using `Vagrant <https://www.vagrantup.com/>`_ to manage VirtualBox VMs.
+(The author of this documentation wasted a lot of time trying to get Vagrant to work with KVM/libvirt.
+Issues with networking that looked like guest misconfigurations vanished after changing to the better-tested combination of Vagrant and VirtualBox.)
 
-1. Install Vagrant, by adding the packages:
-
-  - ``vagrant`` (orchestrating virtual machines on the command line)
-     - Only use when version >= 2.2.16 has become available.  Else see below.
-  - Optional: ``packer`` (for creating your own VM images)
-
-2. Add configuration to install and enable VirtualBox:
-
-  - ``virtualisation.virtualbox.host.enable = true;``
-
-3. Add your user to the ``vboxusers`` group, for example:
-
-  - ``users.extraGroups.vboxusers.members = [ "flo" "jp" ];``
-
-
-.. [#] The author of this documentation wasted a lot of time trying to get Vagrant to work with KVM/libvirt.  Issues with networking that looked like guest misconfigurations vanished after changing to the better-tested combination of Vagrant and VirtualBox.
 
+Use the local development environment
+`````````````````````````````````````
 
-Pre-Vagrant 2.2.16: Get Vagrant with the required fixes for NixOS guests
-````````````````````````````````````````````````````````````````````````
+1. Enter the morph local grid directory::
 
-The Vagrant nixos-guest template `received a critical update on 2021-03-08 <https://github.com/hashicorp/vagrant/commit/990d94ed9d0b3092e855bc1bb9deeeb7aa7792cf>`_ which came out with Vagrant version 2.2.16.
+    cd morph/grid/local
 
-If you run an older Nixpkgs, retrieve and use the latest Vagrant development version like so::
+2. Enter the project's nix-shell::
 
-  NIX_PATH=nixpkgs=https://github.com/NixOS/nixpkgs/archive/refs/heads/master.tar.gz nix-shell -p vagrant
+    nix-shell ../../../shell.nix
 
-
-Use the local development environment
-`````````````````````````````````````
-
-1. Build and start the VMs::
+3. Build and start the VMs::
 
     VAGRANT_DEFAULT_PROVIDER=virtualbox vagrant up
 
-2. Then, once::
+4. Then, add the Vagrant SSH configuration to your user's ``~/.ssh/config`` file::
 
-    vagrant ssh-config > ./vagrant-ssh-config
+    install -d ~/.ssh ; vagrant ssh-config >> ~/.ssh/config
 
-3. Edit the output: Add the IPs from ``grid.nix`` to the ``vagrant-ssh-config`` **Host match blocks** so the config reads like::
+5. Edit the generated configuration: Add the ``publicIP`` addresses from ``grid.nix`` to ssh config **Host** match blocks (**not** HostName) so the ``Host`` lines all read like::
 
     Host payments1 192.168.67.21
-      HostName 192.168.67.21
+      HostName 127.0.0.1
       User vagrant
-      Port 22
       [...]
 
-4.  Then, make morph use this ssh config either - with newer morph [#]_ - by pointing it to it::
-
-     export SSH_CONFIG_FILE=./vagrant-ssh-config
+  Latest Morph honors the ``SSH_CONFIG_FILE`` environment variable (`since 3f90aa88 (March 2020, v 1.5.0) <https://github.com/DBCDK/morph/commit/3f90aa885fac1c29fce9242452fa7c0c505744ef#diff-d155ad793bd62e6ea4c44ba985049ecb13a4f4f32f799791b2bce695a16c0101>`_), so in the future this should get a bit more convenient.
 
-  Or, with older morph, adding the config to your user's ``~/.ssh/config`` file.
+6. Add your SSH key to ``users.nix`` so you'll be able to log in after deploying the new configuration::
 
-  .. [#]  Morph honors the ``SSH_CONFIG_FILE`` environment variable `since 3f90aa88 (March 2020, v 1.5.0) <https://github.com/DBCDK/morph/commit/3f90aa885fac1c29fce9242452fa7c0c505744ef#diff-d155ad793bd62e6ea4c44ba985049ecb13a4f4f32f799791b2bce695a16c0101>`_.
+    $EDITOR secrets/users.nix
 
-5. Then, build and deploy our software to the Vagrant VMs::
+7. Then, build and deploy our software to the Vagrant VMs::
 
     morph build grid.nix
     morph push grid.nix
+    morph deploy grid.nix boot
+    vagrant halt
+    vagrant up
     morph upload-secrets grid.nix
-    morph deploy grid.nix switch
 
-  You will now be able to log in with the users and keys you set in your ``localdev-users.nix`` file.
+  You should now be able to log in with the users and keys you set in your ``users.nix`` file.
 
diff --git a/morph/grid/local/Vagrantfile b/morph/grid/local/Vagrantfile
index 761812c83be88e6bc9196acc06ea405b51573d8c..82bbef1063b108829261670fdceb2e27af8d6764 100644
--- a/morph/grid/local/Vagrantfile
+++ b/morph/grid/local/Vagrantfile
@@ -14,6 +14,10 @@ Vagrant.configure("2") do |config|
     config.vm.box_version = "20.09"
     config.vm.box_check_update = false
     config.vm.network "private_network", ip: "192.168.67.21"
+    # Add self signed SSL key for zkap-issuer:
+    config.vm.provision "file", source: "secrets/payments-localdev-ssl", destination: "/tmp/payments-localdev-ssl"
+    config.vm.provision "shell", inline: "sudo mkdir -p /var/lib/letsencrypt/live/payments.localdev/"
+    config.vm.provision "shell", inline: "sudo mv /tmp/payments-localdev-ssl/* /var/lib/letsencrypt/live/payments.localdev/"
   end
 
   config.vm.define "storage1" do |config|
diff --git a/morph/grid/local/config.json b/morph/grid/local/config.json
index c08955eb24d0c7046dbb4862cf7b81d1ca3e0a0a..38f00367bf2fa36ad7663c89f7849146783b8515 100644
--- a/morph/grid/local/config.json
+++ b/morph/grid/local/config.json
@@ -1,10 +1,10 @@
 { "publicStoragePort": 8898
-, "ristrettoSigningKeyPath": "../../PrivateStorageSecrets/ristretto.signing-key"
-, "stripeSecretKeyPath": "../../PrivateStorageSecrets/privatestorageio-testing-stripe.secret"
-, "monitoringvpnKeyDir": "../../PrivateStorageSecrets/monitoringvpn"
+, "ristrettoSigningKeyPath": "./secrets/ristretto.signing-key"
+, "stripeSecretKeyPath": "./secrets/stripe.secret"
+, "monitoringvpnKeyDir": "./secrets/monitoringvpn"
 , "monitoringvpnEndpoint": "192.168.67.24:51820"
 , "passValue": 1000000
-, "issuerDomain": "payments.localdev"
+, "issuerDomains": ["payments.localdev"]
 , "letsEncryptAdminEmail": "florian@privatestorage.io"
 , "allowedChargeOrigins": [
     "http://localhost:5000"
diff --git a/morph/grid/local/grid.nix b/morph/grid/local/grid.nix
index b71c6e614de9eeedc2cd7ed6f3d0b8fb289e9268..0c114ccf11b9ddb963d5e2e6297b6a7c83792aba 100644
--- a/morph/grid/local/grid.nix
+++ b/morph/grid/local/grid.nix
@@ -6,7 +6,10 @@ import ../../lib/make-grid.nix {
   config = ./config.json;
   nodes = cfg:
   let
-    sshUsers = import ../../../../PrivateStorageSecrets/localdev-users.nix;
+    sshUsers = import ./secrets/users.nix;
+
+    # Get absolute vpn key directory path, as a string:
+    monitoringvpnKeyDir = toString ./. + "/${cfg.monitoringvpnKeyDir}";
 
     # TBD: derive these automatically:
     hostsMap = {
@@ -19,31 +22,34 @@ import ../../lib/make-grid.nix {
     nodeExporterTargets = [ "monitoring1" "payments1" "storage1" "storage2" ];
 
   in {
-    "payments1" = import ../../lib/make-issuer.nix (rec {
+    "payments1" = import ../../lib/make-issuer.nix (cfg // rec {
       publicIPv4 = "192.168.67.21";
       monitoringvpnIPv4 = "172.23.23.11";
       hardware = import ./virtual-hardware.nix ({ inherit publicIPv4; });
       stateVersion = "19.03";
+      inherit monitoringvpnKeyDir;
       inherit sshUsers;
-    } // cfg);
+    });
 
-    "storage1" = import ../../lib/make-testing.nix (rec {
+    "storage1" = import ../../lib/make-testing.nix (cfg // rec {
       publicIPv4 = "192.168.67.22";
       monitoringvpnIPv4 = "172.23.23.12";
       hardware = import ./virtual-hardware.nix ({ inherit publicIPv4; });
       stateVersion = "19.09";
+      inherit monitoringvpnKeyDir;
       inherit sshUsers;
-    } // cfg);
+    });
 
-    "storage2" = import ../../lib/make-testing.nix (rec {
+    "storage2" = import ../../lib/make-testing.nix (cfg // rec {
       publicIPv4 = "192.168.67.23";
       monitoringvpnIPv4 = "172.23.23.13";
       hardware = import ./virtual-hardware.nix ({ inherit publicIPv4; });
       stateVersion = "19.09";
+      inherit monitoringvpnKeyDir;
       inherit sshUsers;
-    } // cfg);
+    });
 
-    "monitoring1" = import ../../lib/make-monitoring.nix (rec {
+    "monitoring1" = import ../../lib/make-monitoring.nix (cfg // rec {
       publicIPv4 = "192.168.67.24";
       monitoringvpnIPv4 = "172.23.23.1";
       inherit vpnClientIPs;
@@ -51,7 +57,8 @@ import ../../lib/make-grid.nix {
       inherit nodeExporterTargets;
       hardware = import ./virtual-hardware.nix ({ inherit publicIPv4; });
       stateVersion = "19.09";
+      inherit monitoringvpnKeyDir;
       inherit sshUsers;
-    } // cfg);
+    });
   };
 }
diff --git a/morph/grid/local/secrets/monitoringvpn/172.23.23.11.key b/morph/grid/local/secrets/monitoringvpn/172.23.23.11.key
new file mode 100644
index 0000000000000000000000000000000000000000..22f11b7bc230a1a1b08d68850caa375171cc386d
--- /dev/null
+++ b/morph/grid/local/secrets/monitoringvpn/172.23.23.11.key
@@ -0,0 +1 @@
+cLP62YAYoA7FY+OhSLR64DIHekOjGGQlfJAWp5cYP00=
diff --git a/morph/grid/local/secrets/monitoringvpn/172.23.23.11.pub b/morph/grid/local/secrets/monitoringvpn/172.23.23.11.pub
new file mode 100644
index 0000000000000000000000000000000000000000..44c0d84b7ad9c50c75c379fd7589dd33370b3e58
--- /dev/null
+++ b/morph/grid/local/secrets/monitoringvpn/172.23.23.11.pub
@@ -0,0 +1 @@
+GYNjLkoyQ1d3OMymYbgq40WAHIUzrSEGBWXvxqceF00=
diff --git a/morph/grid/local/secrets/monitoringvpn/172.23.23.12.key b/morph/grid/local/secrets/monitoringvpn/172.23.23.12.key
new file mode 100644
index 0000000000000000000000000000000000000000..e717bf7ec42129926ceae6c7a44372738162378e
--- /dev/null
+++ b/morph/grid/local/secrets/monitoringvpn/172.23.23.12.key
@@ -0,0 +1 @@
+qFjBtvJKBchzl2HwFvEDoe3zFzyc10osiRlP8HOk2n0=
diff --git a/morph/grid/local/secrets/monitoringvpn/172.23.23.12.pub b/morph/grid/local/secrets/monitoringvpn/172.23.23.12.pub
new file mode 100644
index 0000000000000000000000000000000000000000..18110f20c3bb734fd229f1bd7f87f37050130b6a
--- /dev/null
+++ b/morph/grid/local/secrets/monitoringvpn/172.23.23.12.pub
@@ -0,0 +1 @@
+veio/0E0sJYOjwp3E8EccCyME1pqjkZr4R6whFMdrhs=
diff --git a/morph/grid/local/secrets/monitoringvpn/172.23.23.13.key b/morph/grid/local/secrets/monitoringvpn/172.23.23.13.key
new file mode 100644
index 0000000000000000000000000000000000000000..6dd5087e19e8e9e97f3b459a30563555d10de155
--- /dev/null
+++ b/morph/grid/local/secrets/monitoringvpn/172.23.23.13.key
@@ -0,0 +1 @@
+8HlKTvxZBAZeww6JaNk9kBPjSfT0pVMbDJbzV67yUGE=
diff --git a/morph/grid/local/secrets/monitoringvpn/172.23.23.13.pub b/morph/grid/local/secrets/monitoringvpn/172.23.23.13.pub
new file mode 100644
index 0000000000000000000000000000000000000000..d80b7abbfa20982e69772e6cd66f20dea946582d
--- /dev/null
+++ b/morph/grid/local/secrets/monitoringvpn/172.23.23.13.pub
@@ -0,0 +1 @@
+4VlUMl9FubrLWaN0pRvfdNjjRBQzfCVLMA2lU7OwPzA=
diff --git a/morph/grid/local/secrets/monitoringvpn/preshared.key b/morph/grid/local/secrets/monitoringvpn/preshared.key
new file mode 100644
index 0000000000000000000000000000000000000000..4389e80504f524ecb9b8ee2e8248882da55a7097
--- /dev/null
+++ b/morph/grid/local/secrets/monitoringvpn/preshared.key
@@ -0,0 +1 @@
+E7KTLVnWMmP/mIEkU8WX2DBZJaeMS2+sYArRZuGT1o4=
diff --git a/morph/grid/local/secrets/monitoringvpn/server.key b/morph/grid/local/secrets/monitoringvpn/server.key
new file mode 100644
index 0000000000000000000000000000000000000000..01058684fdb2438b4bff23f0370939628c3ef18c
--- /dev/null
+++ b/morph/grid/local/secrets/monitoringvpn/server.key
@@ -0,0 +1 @@
+iOp2pk2HWyNgRnke7nJgFwodkTWMyHRIKwe8pk+bN3M=
diff --git a/morph/grid/local/secrets/monitoringvpn/server.pub b/morph/grid/local/secrets/monitoringvpn/server.pub
new file mode 100644
index 0000000000000000000000000000000000000000..188c4b4cdea11ca02add26cba104ce61af0ee4c1
--- /dev/null
+++ b/morph/grid/local/secrets/monitoringvpn/server.pub
@@ -0,0 +1 @@
+ojo+p9ZE03GN66ewoIlrHmyV7ICt+2LV32Prs66JsA4=
diff --git a/morph/grid/local/secrets/payments-localdev-ssl/cert.pem b/morph/grid/local/secrets/payments-localdev-ssl/cert.pem
new file mode 100644
index 0000000000000000000000000000000000000000..5300ed591899cb802f2ceff49ac5aa195bb87d12
--- /dev/null
+++ b/morph/grid/local/secrets/payments-localdev-ssl/cert.pem
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIDGTCCAgGgAwIBAgIUM9YnOMe2yYQuCLpYiI2TpZg21AMwDQYJKoZIhvcNAQEL
+BQAwHDEaMBgGA1UEAwwRcGF5bWVudHMubG9jYWxkZXYwHhcNMjEwNjA5MTQzNzU4
+WhcNMzEwNjA3MTQzNzU4WjAcMRowGAYDVQQDDBFwYXltZW50cy5sb2NhbGRldjCC
+ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOBCvkMprdRjD8ECeweMVTFT
+pILu5VA8HC8XK15Y4iE8G48cSYE3m/e6w5g4JvfXgED9eY+1/ZazQXVI6sojnfHL
+Vj9XqsUww3IVtq2Zn+0B8YTrbNxcDH77mUx0mUziAm1bglmoNbd4+q0Fe8FhK/EN
+Jj7UppRjD2ziV9Mw7UT1JjMbfSo7/7ZOV2cWk+hezOywsE+3BM6mju1aHbbYryAd
+EzRwfQKcI/9PC84Bck9e+tEiWQImBf8DguQ+ChuSOmGtP1rI+hq5HfCimH0NCCNS
+iUfNVcubz920FRBeAx9oM0G/u4feeS8T5a1PThZrdhjIycEmU5wsM7F34RzkuEkC
+AwEAAaNTMFEwHQYDVR0OBBYEFAbBfAFOlx4c53CNgsRlobFwUIBbMB8GA1UdIwQY
+MBaAFAbBfAFOlx4c53CNgsRlobFwUIBbMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZI
+hvcNAQELBQADggEBACD+47l30gGaZBPvuCMc/CENLmuqZqj7WGGmWcmUAzsdEgHt
+Q1zhj4un4u4qQ78jeT+cjqNK9qartQIjxzYTpn6lKDBpM9sS2G7RdLxOWWRVAXQj
+Cv6l0tEQArIqqMnzmECe0VDCc97kTg6tnMLFeyQL29XSSNIpS0DiQ+NC69fyrF2E
+vt2bAi+QTlN1U1ZGDJqWlwqkpI3xTRDJnmRPuVNrt07gkMWeSJHEI98Qcve7Ujf3
+hFcAnbshJ8iXxbNjTFIQcCJzHq1UF+eZo8BF1ixYmrk/jmaLNBOSy93tOk1XeJfS
+XtV6X9jmAE20V0XV8uCli6cDxQQYthll/1q5JYQ=
+-----END CERTIFICATE-----
diff --git a/morph/grid/local/secrets/payments-localdev-ssl/chain.pem b/morph/grid/local/secrets/payments-localdev-ssl/chain.pem
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/morph/grid/local/secrets/payments-localdev-ssl/privkey.pem b/morph/grid/local/secrets/payments-localdev-ssl/privkey.pem
new file mode 100644
index 0000000000000000000000000000000000000000..36b699fab9e7dac880daf89ae3f9a7ea9ce732ef
--- /dev/null
+++ b/morph/grid/local/secrets/payments-localdev-ssl/privkey.pem
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDgQr5DKa3UYw/B
+AnsHjFUxU6SC7uVQPBwvFyteWOIhPBuPHEmBN5v3usOYOCb314BA/XmPtf2Ws0F1
+SOrKI53xy1Y/V6rFMMNyFbatmZ/tAfGE62zcXAx++5lMdJlM4gJtW4JZqDW3ePqt
+BXvBYSvxDSY+1KaUYw9s4lfTMO1E9SYzG30qO/+2TldnFpPoXszssLBPtwTOpo7t
+Wh222K8gHRM0cH0CnCP/TwvOAXJPXvrRIlkCJgX/A4LkPgobkjphrT9ayPoauR3w
+oph9DQgjUolHzVXLm8/dtBUQXgMfaDNBv7uH3nkvE+WtT04Wa3YYyMnBJlOcLDOx
+d+Ec5LhJAgMBAAECggEARI3in6FkFCLcNAJQHbSWbmfFSIlC7E4Tx4lrpoHBTquT
+OSJKjgez0/zxwdyYfPcRq8xQls/pX2IYxoOt0nEk3T9tdBuWhoUrmfptR5BIxSjs
+7dcSBiLVZxP+ftK98jS8zTVGGaZEFXwUFUQx2qGbzypX4Kkc6wuFMaHXeyXfwk4j
+v3lOTpT7kX/3iVXzQU9Y5m45W1bbt3lSV4yCndOt/4Xg5d4ue6UcUsyg/r8m5wsU
+nCc14L0HsY/VCCRISmDwa4UoqHGfQv6+XXxLT8gJM7H8PkZK8TRV/ZG2Yug63wDS
+xkhMZBodGoYuQpB19Y+NxjwG8Se7RxcCAyS7zcYcCQKBgQD/VWesc2/TplikifQf
+LJl+utiQ9G/o3wAquAKInxQOcUxOm1fsvjgsLfrjpAVj0D3qD0Sf3lSp7z3tpUMf
+MSi9MgNunSU7pKST7jex6BJNU0TTRcFnoqcIlUYiDoLLbkkVlf8Nome88gkwJRKZ
+RjI+6ES9hGaM85QEgjdtRNGXIwKBgQDg2JPfVXYyrOIITus/DHHv8em1RuGCIe6X
+T3M0+GgJ27hOEeneAyzk4aWvVzE3/3lxx55ePGRAz43jPrUxn0kfy/Jx4UMKsEW+
+XzR7CfxTxamUx/mq2bbqk+bbnXZZQedAip4eQ23GbWdrE4/v83JXzWj355yUQCQk
+uuIcda7fowKBgEY20CmmHOxQ5DNrFEy2UQd+jitebJ/XIw6cR2YWiMdn9JnxMf6S
+WJQdmM6cvjayfzQsOqzT0OhiN99wAMNFG3TbmgIDCMgcAH4Flh9AODg3W8fVeNfs
+7I35rq2S2/jhPQvIkbjIHkrhLBGnQDQSD6Mo8C5FiIXePaf3vxI3SIONAoGAZdOv
+pEUf8nM5KmoTP8pzDyePn/kpx7V2SDBDDIozE8PeA/043MKzYjSOxInIUIPyjATL
+RAI1pORabb/Ib2CjzTKf6dMKeZy6+SxEqDQtggLSef7WovlWTYYN1wfIwUOHZ0Nf
+uHTxEhwZ6fRCC3lFH153W04ZK0qhE8FPBXSGbeECgYBcY0QmOd3ao3v0xscKmPlM
+E9MICgwgEJ6pAsaOI30OHqUAtlqrlw5ph3skxlLUpHWW6OUczUUV6nwguk4piuee
+1J9uuA02FlIvvUy5Bsrgqlu0Lj5vJ8Im2Go9dJ8k/m9J5BUgDdPuNwnmjiyNQurd
+Fl50FWx/3WeJB2qK981vmw==
+-----END PRIVATE KEY-----
diff --git a/morph/grid/local/secrets/ristretto.signing-key b/morph/grid/local/secrets/ristretto.signing-key
new file mode 100644
index 0000000000000000000000000000000000000000..8726b7060694499f61cc15ba826f3933b88795a5
--- /dev/null
+++ b/morph/grid/local/secrets/ristretto.signing-key
@@ -0,0 +1 @@
+NAQBkEEUKPDtq8af5anlHvWMjeSVoH56RnpCTy70QwA=
\ No newline at end of file
diff --git a/morph/grid/local/secrets/stripe.secret b/morph/grid/local/secrets/stripe.secret
new file mode 100644
index 0000000000000000000000000000000000000000..ebf3fdabcd222af9ca7ed46bf0b45a2fd18a603e
--- /dev/null
+++ b/morph/grid/local/secrets/stripe.secret
@@ -0,0 +1 @@
+sk_test_Dr+XLVjkC0oO3Zw8Ws0yWtDLqR1sM+/fmw
diff --git a/morph/grid/local/secrets/users.nix b/morph/grid/local/secrets/users.nix
new file mode 100644
index 0000000000000000000000000000000000000000..93a8b660c78fa12b1e20c6d560f78efb1b5684c7
--- /dev/null
+++ b/morph/grid/local/secrets/users.nix
@@ -0,0 +1,4 @@
+# Add your public key. Example: 
+# let key = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIHx7wJQNqKn8jOC4AxySRL2UxidNp7uIK9ad3pMb1ifF flo@fs-la";
+let key = undefined;
+in { "root" = key; "vagrant" = key; }
diff --git a/morph/grid/production/.gitignore b/morph/grid/production/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..db2fc0de62d01d6d7eec83f8f3e8c3b13b20392a
--- /dev/null
+++ b/morph/grid/production/.gitignore
@@ -0,0 +1 @@
+secrets
diff --git a/morph/grid/production/config.json b/morph/grid/production/config.json
index ec60acc70dcdc90409b84e0b19ce9c2cb3d27cfa..e71cb8b4b5f999e3059f0669c2bc3f92f29242a6 100644
--- a/morph/grid/production/config.json
+++ b/morph/grid/production/config.json
@@ -1,11 +1,16 @@
 { "publicStoragePort": 8898
-, "ristrettoSigningKeyPath": "../../PrivateStorageSecrets/ristretto.signing-key"
-, "stripeSecretKeyPath": "../../PrivateStorageSecrets/stripe.secret"
+, "ristrettoSigningKeyPath": "./secrets/ristretto.signing-key"
+, "stripeSecretKeyPath": "./secrets/stripe.secret"
 , "passValue": 1000000
-, "issuerDomain": "payments.privatestorage.io"
+, "issuerDomains": [
+    "payments.privatestorage.io"
+  , "payments.private.storage"
+  ]
 , "letsEncryptAdminEmail": "jean-paul@privatestorage.io"
 , "allowedChargeOrigins": [
     "https://privatestorage.io"
   , "https://www.privatestorage.io"
+  , "https://private.storage"
+  , "https://www.private.storage"
   ]
 }
diff --git a/morph/grid/production/grid.nix b/morph/grid/production/grid.nix
index 7c9abe142fa98a6ceeebb3c8dc6d53dec2622e8c..f5735d259dbff27f1d9cabbbca512af81d4550bb 100644
--- a/morph/grid/production/grid.nix
+++ b/morph/grid/production/grid.nix
@@ -6,7 +6,7 @@ import ../../lib/make-grid.nix {
   config = ./config.json;
   nodes = cfg:
     let
-      sshUsers = import ../../../../PrivateStorageSecrets/production-users.nix;
+      sshUsers = import ./secrets/users.nix;
     in {
     # Here are the hosts that are in this morph network.  This is sort of like
     # a server manifest.  We try to keep as many of the specific details as
diff --git a/morph/grid/testing/.gitignore b/morph/grid/testing/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..db2fc0de62d01d6d7eec83f8f3e8c3b13b20392a
--- /dev/null
+++ b/morph/grid/testing/.gitignore
@@ -0,0 +1 @@
+secrets
diff --git a/morph/grid/testing/config.json b/morph/grid/testing/config.json
index d0b1b814601ec076aa3fb77b55e589f8e7c5782d..f5aed85fe73e24b8403b4bce9cb34aefc8091d34 100644
--- a/morph/grid/testing/config.json
+++ b/morph/grid/testing/config.json
@@ -1,10 +1,14 @@
 { "publicStoragePort": 8898
-, "ristrettoSigningKeyPath": "../../PrivateStorageSecrets/ristretto.signing-key"
-, "stripeSecretKeyPath": "../../PrivateStorageSecrets/privatestorageio-testing-stripe.secret"
-, "monitoringvpnKeyDir": "../../PrivateStorageSecrets/monitoringvpn"
+, "ristrettoSigningKeyPath": "./secrets/ristretto.signing-key"
+, "stripeSecretKeyPath": "./secrets/privatestorageio-testing-stripe.secret"
+, "monitoringvpnKeyDir": "./secrets/monitoringvpn"
 , "monitoringvpnEndpoint": "monitoring.privatestorage-staging.com:51820"
+, "stripeSecretKeyPath": "./secrets/stripe.secret"
 , "passValue": 1000000
-, "issuerDomain": "payments.privatestorage-staging.com"
+, "issuerDomains": [
+    "payments.privatestorage-staging.com"
+  , "payments.extra.privatestorage-staging.com"
+  ]
 , "letsEncryptAdminEmail": "jean-paul@privatestorage.io"
 , "allowedChargeOrigins": [
     "http://localhost:5000"
diff --git a/morph/grid/testing/grid.nix b/morph/grid/testing/grid.nix
index ed002cbe0a23a60874201c1dbf6bea15a36a1d87..e31a28f2eb7817f393f4e8b6b71972b7fd2f79f1 100644
--- a/morph/grid/testing/grid.nix
+++ b/morph/grid/testing/grid.nix
@@ -6,49 +6,49 @@ import ../../lib/make-grid.nix {
   config = ./config.json;
   nodes = cfg:
   let
-    importDef = default: path: (
-      if builtins.pathExists path
-      then import path
-      else default
-    );
-    sshUsers = importDef {} ../../../../PrivateStorageSecrets/staging-users.nix;
+    sshUsers = import ./secrets/users.nix;
+
+    # Get absolute vpn key directory path, as a string:
+    monitoringvpnKeyDir = toString ./. + "/${cfg.monitoringvpnKeyDir}";
 
     # TBD: derive these automatically:
     hostsMap = {
       "172.23.23.1"  = [ "monitoring" "monitoring.monitoringvpn" ];
-      "172.23.23.11" = [ "payments" "payments.monitoringvpn" ];
+      "172.23.23.11" = [ "payments"   "payments.monitoringvpn"   ];
       "172.23.23.12" = [ "storage001" "storage001.monitoringvpn" ];
     };
     vpnClientIPs = [ "172.23.23.11" "172.23.23.12" ];
     nodeExporterTargets = [ "monitoring" "payments" "storage001" ];
 
   in {
-    "payments" = import ../../lib/make-issuer.nix ({
-      publicIPv4 = "18.197.42.120";
+    "payments" = import ../../lib/make-issuer.nix (cfg // {
+      publicIPv4 = "18.194.183.13";
       monitoringvpnIPv4 = "172.23.23.11";
+      inherit monitoringvpnKeyDir;
       inherit sshUsers;
       hardware = ../../lib/issuer-aws.nix;
       stateVersion = "19.03";
-    } // cfg);
+    });
 
     "storage001" = import ../../lib/make-testing.nix (cfg // {
       publicIPv4 = "3.120.26.190";
       monitoringvpnIPv4 = "172.23.23.12";
+      inherit monitoringvpnKeyDir;
       inherit sshUsers;
       hardware = ./testing001-hardware.nix;
       stateVersion = "19.03";
     });
 
-    "monitoring" = import ../../lib/make-monitoring.nix ({
+    "monitoring" = import ../../lib/make-monitoring.nix (cfg // {
       publicIPv4 = "18.156.171.217";
       monitoringvpnIPv4 = "172.23.23.1";
+      inherit monitoringvpnKeyDir;
       inherit vpnClientIPs;
       inherit hostsMap;
       inherit nodeExporterTargets;
-      nginxExporterTargets = [ ];
       hardware = ../../lib/issuer-aws.nix;
       stateVersion = "19.09";
       inherit sshUsers;
-    } // cfg);
+    });
   };
 }
diff --git a/morph/lib/make-issuer.nix b/morph/lib/make-issuer.nix
index 7ca7992c94776af560c667f2c0949cb1bfdaed65..bbdf0cebbf770738e9ccb997daec75e58df021b5 100644
--- a/morph/lib/make-issuer.nix
+++ b/morph/lib/make-issuer.nix
@@ -1,7 +1,7 @@
 { hardware
 , ristrettoSigningKeyPath
 , stripeSecretKeyPath
-, issuerDomain
+, issuerDomains
 , letsEncryptAdminEmail
 , allowedChargeOrigins
 , sshUsers
@@ -76,7 +76,7 @@ in rec {
     database = "SQLite3";
     databasePath = "/var/db/vouchers.sqlite3";
     inherit letsEncryptAdminEmail;
-    domain = issuerDomain;
+    domains = issuerDomains;
     inherit allowedChargeOrigins;
   };
 
@@ -86,5 +86,6 @@ in rec {
     enable = true;
     ip = monitoringvpnIPv4;
     endpoint = monitoringvpnEndpoint;
+    endpointPublicKeyFile = monitoringvpnKeyDir + "/server.pub";
   };
 }
diff --git a/morph/lib/make-monitoring.nix b/morph/lib/make-monitoring.nix
index acd8c1e924f48b5a838b209e4bb09d21f34f368f..8eb53c6db9552e65a84e5d3e5564449db437e902 100644
--- a/morph/lib/make-monitoring.nix
+++ b/morph/lib/make-monitoring.nix
@@ -40,8 +40,7 @@ in rec {
 
   deployment = {
     targetHost = publicIPv4;
-
-    secrets = { } // vpnSecrets;
+    secrets = vpnSecrets;
   };
 
   imports = [
@@ -58,6 +57,7 @@ in rec {
     enable = true;
     ip = monitoringvpnIPv4;
     inherit vpnClientIPs;
+    pubKeysPath = monitoringvpnKeyDir;
   };
 
   services.private-storage.monitoring.grafana = {
diff --git a/morph/lib/make-testing.nix b/morph/lib/make-testing.nix
index 974cfcbb69d8160bef682ccaf2bbdeda197b1f41..3f6e767db5ee734a8ca2314b216d4fa602c01907 100644
--- a/morph/lib/make-testing.nix
+++ b/morph/lib/make-testing.nix
@@ -75,5 +75,6 @@ in rec {
     enable = true;
     ip = monitoringvpnIPv4;
     endpoint = monitoringvpnEndpoint;
+    endpointPublicKeyFile = monitoringvpnKeyDir + "/server.pub";
   };
 }
diff --git a/nixos/modules/issuer.nix b/nixos/modules/issuer.nix
index 6ad4f1b8fa01d570e8b6e1d5c4acfbeb42757822..fb93ce35cce8c9cadbad5a04e888b0cca991f9c7 100644
--- a/nixos/modules/issuer.nix
+++ b/nixos/modules/issuer.nix
@@ -18,12 +18,11 @@ in {
         The package to use for the ZKAP issuer.
       '';
     };
-    services.private-storage-issuer.domain = lib.mkOption {
-      default = "payments.privatestorage.io";
-      type = lib.types.str;
-      example = lib.literalExample "payments.example.com";
+    services.private-storage-issuer.domains = lib.mkOption {
+      type = lib.types.listOf lib.types.str;
+      example = lib.literalExample [ "payments.example.com" ];
       description = ''
-        The domain name at which the issuer is reachable.
+        The domain names at which the issuer is reachable.
       '';
     };
     services.private-storage-issuer.tls = lib.mkOption {
@@ -115,6 +114,10 @@ in {
   config =
     let
       certroot = "/var/lib/letsencrypt/live";
+      # We'll refer to this collection of domains by the first domain in the
+      # list.
+      domain = builtins.head cfg.domains;
+      certServiceName = "cert-${domain}";
     in lib.mkIf cfg.enable {
     # Add a systemd service to run PaymentServer.
     systemd.services.zkapissuer = {
@@ -124,7 +127,7 @@ in {
 
       # Make sure we have a certificate the first time, if we are running over
       # TLS and require a certificate.
-      requires = lib.optional cfg.tls "cert-${cfg.domain}.service";
+      requires = lib.optional cfg.tls "${certServiceName}.service";
 
       after = [
         # Make sure there is a network so we can bind to all of the
@@ -133,7 +136,7 @@ in {
       ] ++
         # Make sure we run after the certificate is issued, if we are running
         # over TLS and require a certificate.
-        lib.optional cfg.tls "cert-${cfg.domain}.service";
+        lib.optional cfg.tls "${certServiceName}.service";
 
       # It really shouldn't ever exit on its own!  If it does, it's a bug
       # we'll have to fix.  Restart it and hope it doesn't happen too much
@@ -157,9 +160,9 @@ in {
             if cfg.tls
             then
               "--https-port 443 " +
-              "--https-certificate-path ${certroot}/${cfg.domain}/cert.pem " +
-              "--https-certificate-chain-path ${certroot}/${cfg.domain}/chain.pem " +
-              "--https-key-path ${certroot}/${cfg.domain}/privkey.pem"
+              "--https-certificate-path ${certroot}/${domain}/cert.pem " +
+              "--https-certificate-chain-path ${certroot}/${domain}/chain.pem " +
+              "--https-key-path ${certroot}/${domain}/privkey.pem"
             else
               # Only for automated testing.
               "--http-port 80";
@@ -179,20 +182,20 @@ in {
 
     # Certificate renewal.  We must declare that we *require* it in our
     # service above.
-    systemd.services."cert-${cfg.domain}" = {
+    systemd.services."${certServiceName}" = {
       enable = true;
-      description = "Issue/Renew certificate for ${cfg.domain}";
+      description = "Certificate ${domain}";
       serviceConfig = {
         ExecStart =
         let
           configArgs = "--config-dir /var/lib/letsencrypt --work-dir /var/run/letsencrypt --logs-dir /var/run/log/letsencrypt";
         in
-          pkgs.writeScript "cert-${cfg.domain}-start.sh" ''
+          pkgs.writeScript "cert-${domain}-start.sh" ''
           #!${pkgs.runtimeShell} -e
           # Register if necessary.
           ${pkgs.certbot}/bin/certbot register ${configArgs} --non-interactive --agree-tos -m ${cfg.letsEncryptAdminEmail} || true
           # Obtain the certificate.
-          ${pkgs.certbot}/bin/certbot certonly ${configArgs} --non-interactive --standalone --domains ${cfg.domain}
+          ${pkgs.certbot}/bin/certbot certonly ${configArgs} --non-interactive --standalone --expand --domains ${builtins.concatStringsSep "," cfg.domains}
           '';
       };
     };
diff --git a/nixos/modules/monitoring/vpn/client.nix b/nixos/modules/monitoring/vpn/client.nix
index 4c651f612ef7d906a44efd99e68a054b7708c912..dbd50b82ef5b09495e332e6fbb7ac5676f5ac322 100644
--- a/nixos/modules/monitoring/vpn/client.nix
+++ b/nixos/modules/monitoring/vpn/client.nix
@@ -49,7 +49,6 @@ in {
     endpointPublicKeyFile = lib.mkOption {
       type = lib.types.path;
       example = lib.literalExample ../PrivateStorageSecrets/monitoringvpn/server.pub;
-      default = ../../../../../PrivateStorageSecrets/monitoringvpn/server.pub;
       description = ''
         File with base64 public key generated by <command>cat private.key | wg pubkey > pubkey.pub</command>.
       '';
diff --git a/nixos/modules/monitoring/vpn/server.nix b/nixos/modules/monitoring/vpn/server.nix
index b7f8c00cf74b961ac2e2c4228f824ae8f933b0e5..2374ddc8657fb299fb83155cbabe328cd54c1aaf 100644
--- a/nixos/modules/monitoring/vpn/server.nix
+++ b/nixos/modules/monitoring/vpn/server.nix
@@ -52,7 +52,6 @@ in {
     pubKeysPath = lib.mkOption {
       type = lib.types.path;
       example = lib.literalExample ../PrivateStorageSecrets/monitoringvpn;
-      default = ../../../../../PrivateStorageSecrets/monitoringvpn;
       description = ''
         The path to the directory that holds the public keys.
       '';
diff --git a/nixos/modules/ssh.nix b/nixos/modules/ssh.nix
index 59ee2fec949be247143041379626f35b7d8bf657..667bdd26215b4e0978781244741dd4c5313cefbd 100644
--- a/nixos/modules/ssh.nix
+++ b/nixos/modules/ssh.nix
@@ -37,6 +37,10 @@
         # password-based authentication at all.
         PermitEmptyPasswords no
 
+        # Agent forwarding is fraught.  It can be used by an attacker to
+        # leverage one compromised system into more.  Discourage its use.
+        AllowAgentForwarding no
+
         # Only allow authentication as one of the configured users, not random
         # other (often system-managed) users.  Possibly this is also
         # superfluous!  NixOS system users have nologin as their shell ... so they
diff --git a/nixos/modules/tests/get-passes.py b/nixos/modules/tests/get-passes.py
index 691a3d26f37125379df85c85481cfdac67741d07..63b59e5700e0fa0c659f04df8fa889a0501f125f 100755
--- a/nixos/modules/tests/get-passes.py
+++ b/nixos/modules/tests/get-passes.py
@@ -95,7 +95,7 @@ def charge_json(voucher):
     return {
         "token": "tok_abcdef",
         "voucher": voucher,
-        "amount": "100",
+        "amount": "650",
         "currency": "USD",
     }
 
diff --git a/nixos/modules/tests/private-storage.nix b/nixos/modules/tests/private-storage.nix
index e085f8bc7142da4067745bdee233c82e7b1e8d1c..cbf4c5937ca6780ce9e931d6ceec91c29643fbc3 100644
--- a/nixos/modules/tests/private-storage.nix
+++ b/nixos/modules/tests/private-storage.nix
@@ -134,7 +134,7 @@ in {
 
       services.private-storage-issuer = {
         enable = true;
-        domain = "issuer";
+        domains = ["issuer"];
         tls = false;
         issuer = "Ristretto";
         inherit ristrettoSigningKeyPath;
diff --git a/nixpkgs-2105.json b/nixpkgs-2105.json
new file mode 100644
index 0000000000000000000000000000000000000000..d441d00995a78a20cc8677a85ced2a675a9502ae
--- /dev/null
+++ b/nixpkgs-2105.json
@@ -0,0 +1,4 @@
+{ "name": "stable2105"
+, "url": "https://releases.nixos.org/nixos/21.05/nixos-21.05.804.5de44c15758/nixexprs.tar.xz"
+, "sha256": "002zvc16hyrbs0icx1qj255c9dqjpdxx4bhhfjndlj3kwn40by0m"
+}
diff --git a/nixpkgs.json b/nixpkgs.json
index a13edba683061306533106d1f2d1e5c6eacaadba..33b343ef3498ae226218f59be257e808e9a88c7e 100644
--- a/nixpkgs.json
+++ b/nixpkgs.json
@@ -1,4 +1,4 @@
 { "name": "nixpkgs"
-, "url": "https://github.com/PrivateStorageio/nixpkgs/archive/d7c46f46c3e6acfcde44bb05877fd5c9f07dfe1b.tar.gz"
-, "sha256": "1vddxhp3ljaq9qqzbpjp2kx34ps7nzbsn4zy31x0748a52rlsh0q"
+, "url": "https://github.com/PrivateStorageio/nixpkgs/archive/8c7a61c658e32eaccf666e5fe818a996c36a988f.tar.gz"
+, "sha256": "1ln0a8c20qykm57wl901lixny1fcfmzgbavd7pbjk6jbnfij59bl"
 }
diff --git a/shell.nix b/shell.nix
index b15b7c66b110a785480142c3b1d34b3d365004f6..6e46c9ca0feaa3ab6fbd22c1228ec786a49e79b6 100644
--- a/shell.nix
+++ b/shell.nix
@@ -1,12 +1,12 @@
 let
-  nixpkgs-pin = builtins.fromJSON (builtins.readFile ./nixpkgs.json);
-  nixpkgs-src = builtins.fetchTarball nixpkgs-pin;
-  nixpkgs = import nixpkgs-src { };
+  nixpkgs = import (builtins.fetchTarball (builtins.fromJSON (builtins.readFile ./nixpkgs.json))) { };
+  stable2105 = import (builtins.fetchTarball (builtins.fromJSON (builtins.readFile ./nixpkgs-2105.json))) { };
 in
 { pkgs ? nixpkgs }:
 pkgs.mkShell {
   NIX_PATH = "nixpkgs=${nixpkgs.path}";
   buildInputs = [
     pkgs.morph
+    stable2105.vagrant
   ];
 }