New post about the inverse contact sync cloud
authorRalf Jung <post@ralfj.de>
Mon, 28 May 2018 16:13:40 +0000 (18:13 +0200)
committerRalf Jung <post@ralfj.de>
Mon, 28 May 2018 16:13:40 +0000 (18:13 +0200)
ralf/_posts/2017-12-26-lets-encrypt.md
ralf/_posts/2018-05-28-cloudless-contact-sync.md [new file with mode: 0644]

index 3f4fbee3edd74453ed96b1dcacc87be74e1b7086..e63d77b119b18d6c44d69549b5929cc8b0566ce8 100644 (file)
@@ -1,6 +1,6 @@
 ---
 title: Let's Encrypt Tiny
 ---
 title: Let's Encrypt Tiny
-categories: Sysadmin
+categories: sysadmin
 ---
 
 I think all HTTP communication on the internet should be encrypted -- and thanks
 ---
 
 I think all HTTP communication on the internet should be encrypted -- and thanks
@@ -192,7 +192,7 @@ SSLHonorCipherOrder     on
 [bettercrypto.org](https://bettercrypto.org) because I prefer to not update it
 with every change in OpenSSL's supported ciphers.)
 
 [bettercrypto.org](https://bettercrypto.org) because I prefer to not update it
 with every change in OpenSSL's supported ciphers.)
 
-## Obtaining the first certificate
+## Obtaining the First Certificate
 
 You can now run `letsencrypt-tiny -c letsencrypt.conf init` to perform the
 initial setup.
 
 You can now run `letsencrypt-tiny -c letsencrypt.conf init` to perform the
 initial setup.
@@ -201,7 +201,7 @@ In the future, to change the set of domains, first edit the config file and then
 run `letsencrypt-tiny -c letsencrypt.conf -k renew`.  The `-k` tells Let's
 Encrypt Tiny to also run the certificate hook.
 
 run `letsencrypt-tiny -c letsencrypt.conf -k renew`.  The `-k` tells Let's
 Encrypt Tiny to also run the certificate hook.
 
-## Automation via cron
+## Automation Via Cron
 
 Let's Encrypt certificates expire after 90 days, so we want renewal to be
 automated.  To this end, just make sure that `letsencrypt-tiny -c
 
 Let's Encrypt certificates expire after 90 days, so we want renewal to be
 automated.  To this end, just make sure that `letsencrypt-tiny -c
diff --git a/ralf/_posts/2018-05-28-cloudless-contact-sync.md b/ralf/_posts/2018-05-28-cloudless-contact-sync.md
new file mode 100644 (file)
index 0000000..9b3e190
--- /dev/null
@@ -0,0 +1,165 @@
+---
+title: "Syncing Contacts Without Exposing Them to the Cloud"
+categories: sysadmin
+---
+
+I finally have a setup that I am happy with for syncing contacts between my phone and my laptop.
+Most would probably consider that a solved problem, but I have an extra requirement that rules out most existing solutions:
+
+> *The data of my contacts must not be exposed in clear text to any machine I do not physically control*.
+
+I'm not happy about my own personal data being shared with third parties, and consequently, I will not share the personal data others entrust to me with third parties.
+This obviously rules out all of these "free" cloud services out there that Apple, Google and others offer---services that are paid with data, and in the case of contact sync frequently paid with the data of others.
+(This leaves me wonder whether under the GDPR, it is even legal for someone else to consent to my contact data being shared with a cloud provider. I certainly never consented and still my data sits in multiple synced address books. But that's a discussion for another day.)
+Moreover, this also rules out putting them e.g. into an ownCloud or Nextcloud hosted on ralfj.de, because that is a VPS that I do not have any physical control over.
+
+<!-- MORE -->
+
+I *could* host the contact server on a Raspberry Pi at home and set up dynamic DNS and port forwarding "as usual", but today we are going to look at another solution:
+Hosting the contact server on my laptop, and making that accessible from my phone even when my laptop is behind a non-cooperating NAT or another kind of firewall.
+
+To do this, I need a server with an internet-facing IP.
+However, that server will just serve as a relay between my phone and my laptop, and the data is going to be encrypted, so I can just use ralfj.de for that.
+The plan is to forward the HTTP and HTTPS ports from that IP to the laptop using inverse SSH port forwarding, therefore making the server running on the laptop available to the internet.
+
+I assume you are already familiar with setting up ownCloud or Nextcloud or whatever you want to use (personally, I am using [Radicale](https://github.com/Kozea/Radicale/)), and have it set up on your laptop.
+If not, all of these projects come with the required documentation and the internet is full of tutorials.
+The remaining task, then, is to make this server, which is running on your laptop, available to the entire internet so your phone can access it.
+This requires some configuration on the server, and some configuration on the laptop.
+
+## Setting Up the Server
+
+First we need an IP `$IP` where the HTTP and HTTPS ports are still free, and a hostname `$HOST` pointing to that IP.
+My server has a second IP for various reasons, so that was fairly easy to do.
+If yours does not, you should still be able to achieve this kind of setup through a reverse SNI proxy running on your server; I will come back to that variant later.
+
+We want to use the `-R` option of SSH to forward port 80 and 443 from the server to the laptop.
+However, these are privileged ports that only root can open, so doing this naively would require logging in as root via SSH.
+For security reasons, I have root logins disabled, and I'd rather not change that, so we proceed differently instead: We forward ports 8053 and 44353, and then we have NAT rules on the server that forward packages from ports 80 and 443 to ports 8053 and 44353, respectively.
+
+With [ferm](http://ferm.foo-projects.org/), such an iptables rule would look as follows (here and in the following, replacing `$IP` by whatever IP you are using for this purpose):
+```
+table nat {
+    # PREROUTING applies to packages coming from the outside, OUTPUT
+    # for localhost connections.
+    chain (PREROUTING OUTPUT) {
+        daddr $IP proto tcp dport 80 DNAT to $IP:8053;
+        daddr $IP proto tcp dport 443 DNAT to $IP:44353;
+    }
+}
+```
+The plain iptables equivalent is
+```
+-A PREROUTING -d $IP/32 -p tcp -m tcp --dport 80 -j DNAT --to-destination $IP:8053
+-A PREROUTING -d $IP/32 -p tcp -m tcp --dport 443 -j DNAT --to-destination $IP:44353
+-A OUTPUT -d $IP/32 -p tcp -m tcp --dport 80 -j DNAT --to-destination $IP:8053
+-A OUTPUT -d $IP/32 -p tcp -m tcp --dport 443 -j DNAT --to-destination $IP:44353
+```
+
+Next, we have to configure the SSH daemon to permit reverse port forwarding to be configured by the client.
+There is no reason to do this for all users, so instead we create a new user that is used specifically for this purpose:
+```
+adduser --system inverse-cloud --home /var/lib/inverse-cloud
+```
+Next, we configure SSH for this user by editing `/etc/ssh/sshd_config`:
+```
+Match User inverse-cloud
+       ClientAliveInterval 15
+       GatewayPorts clientspecified
+```
+The second option lets the client control the reverse port forwarding; `ClientAliveInterval` is useful because it makes the server kill the connection if the client does not respond for 45 seconds (three alive intervals).
+When the laptop goes offline, we want to kill the connection on both ends quickly so that the laptop can open a new connection; as long as the old one is still around the ports are still blocked.
+
+Now you just need to put a public SSH key (I suggest you create a new one for this purpose, and it should have no passphrase) into `/var/lib/inverse-cloud/.ssh/authorized_keys`.
+That's already it on the server side!
+You can test if this all works by running
+```
+ssh -R $IP:44353:localhost:443 -R $IP:8053:localhost:80 inverse-cloud@your-server -i ~/.ssh/inverse-cloud
+```
+and then accessing `$HOST` in your browser. If it all worked, that should connect to the webserver on your laptop.
+
+### What If the Ports Are Already Used?
+
+If you don't have an IP where ports 80 and 443 are still free, the approach above will not work.
+Installing the NAT rules will make your main webserver unavailable.
+However, you can still use reverse port forwarding, you just need a reverse proxy in front of it that decides if a connection goes to the local webserver or the ports opened by the laptop.
+
+On port 80, that can be just done with an apache vhost or using nginx (or any other webserver, really) that forwards all requests for `$HOST` to the local port `8053`.
+However, if we did the same with port 443 then nginx *on your server* would terminate the HTTPS tunnel, which violates our goal of not exposing the unencrypted contacts to the server.
+Instead, we use something like [sniproxy](https://github.com/dlundquist/sniproxy).
+I have not actually done this setup, but the rough idea is to make sniproxy listen on port 443, to forward `$HOST` to `$IP:44353` and forward everything else to your main webserver (which has to be configured to listen on a different port).
+Consult the docs of sniproxy and whatever web server you are using for more details.
+
+## Setting up the laptop
+
+Next, we need to set up the laptop.
+First of all, we don't want to manually run `ssh -R ...` all the time, and we also need to take care of reconnecting when the connection fails.
+It turns out there already is a tool for this purpose: [autossh](http://www.harding.motd.ca/autossh/)!
+After installing it, all you need to do is automatically run autossh and it will keep the connection to your server, and therefore the reverse port forwarding, alive.
+If you are using systemd on your laptop, the following service file will do it:
+```
+[Unit]
+Description=inverse-cloud
+After=network-online.target
+
+[Service]
+User=$USER # use your username here
+KillSignal=SIGINT
+Environment="AUTOSSH_GATETIME=0"
+# -M 0 --> no monitoring
+# -N Just open the connection and do nothing (not interactive)
+ExecStart=/usr/bin/autossh -M 0 -N -o "ServerAliveInterval 30" -R $IP:44353:localhost:443 -R $IP:8053:localhost:80 inverse-cloud@your-server -i /home/$USER/.ssh/inverse-cloud
+Restart=on-failure
+
+[Install]
+WantedBy=multi-user.target
+```
+I have set `AUTOSSH_GATETIME` to 0 because I had trouble with autossh quitting after ssh failed due to a lack of DNS resolution when the laptop is offline.
+The `ServerAliveInterval` serves the same purpose as the `ClientAliveInterval` on the server side: The SSH connection is automatically killed after 90 seconds, which will trigger autossh to try again.
+
+Now make sure you terminate the test SSH session from the last section, and start your new service.
+If this works, you should still be able to reach `$HOST` in your browser.
+
+All we still need to do is set up some crypto.
+We are going to obtain an SSL certificate for `$HOST` *for your laptop*, and use that to secure the connection to `https://$HOST`.
+Because only the laptop has the key to this certificate, the server at `$IP` cannot actually decipher the connection, it just forwards the encrypted bytes to the laptop where they are decrypted.
+The easiest way to obtain such a certificate is using [Let's Encrypt](https://letsencrypt.org/).
+I am using my own [Let's Encrypt Tiny]({{ site.baseurl }}{% post_url 2017-12-26-lets-encrypt %}) for this purpose, but you can use any other Let's Encrypt client as well.
+Since `$HOST:80` legitimately *is* your laptop at this point, the laptop should be able to obtain a certificate just fine.
+
+If you are using Radicale like me, just putting Radicale on port 80 is not going to work though as that provides no way to serve the ACME challenge file needed for Let's Encrypt.
+So, I have set up an nginx reverse proxy with the following configuration
+```
+server {
+    server_name $HOST;
+    listen [::]:80;
+# Enable these once you have a certificate
+#    listen [::]:443 ssl;
+#    ssl_certificate ...;
+#    ssl_certificate_key ...;
+#    ssl_dhparam ...;
+
+    # This directory should be empty.
+    root /var/www/$HOST;
+
+    location /.well-known/acme-challenge/ {
+        # You may have to change this directory to match your Let's Encrypt client.
+        alias /var/www/acme-challenge/;
+    }
+
+    # This is the actual reverse proxy.
+    location /radicale/ {
+        proxy_pass http://localhost:35232/;
+    }
+}
+```
+
+## That's It
+
+That's it!
+You can now access your laptop via `https://$HOST`, and you can set up your devices to use that server for contact synchronization.
+On my phone, I am using [DAVdroid](https://f-droid.org/packages/at.bitfire.davdroid/), and on the laptop Thunderbird with the [Inverse SOGo Connector](http://sogo.nu/downloads/frontends.html).
+Whenever both my laptop and my phone are up, they synchronize contacts automatically over a properly end-to-end encrypted channel.
+When my laptop is offline, I get a warning on my phone about not being able to sync, but the sync will resume automatically when the laptop is online again.
+
+Happy hacking!