post(nixos-caddy): update to NixOS 20.09 & caddy 2.1

This commit is contained in:
MDLeom 2020-11-09 00:55:42 +00:00
parent 62ebd35ae1
commit a1c0b6b1d0
No known key found for this signature in database
GPG Key ID: 32D3E28E96A695E8
5 changed files with 407 additions and 398 deletions

View File

@ -133,7 +133,7 @@ shred -uz configuration.7z configuration.nix
Following is my "configuration.nix". I'll show you how to secure NixOS using hashed password, firewall, DNS-over-TLS and USBGuard in my next post. After that, I'll show you how to setup Caddy and Tor (they are disabled for now).
```
``` nix /etc/nixos/configuration.nix
{ config, pkgs, ... }:
{

View File

@ -2,7 +2,7 @@
title: "Setup Caddy as a reverse proxy on NixOS (Part 2: Hardening)"
excerpt: "Part 2: Securing NixOS"
date: 2020-03-04
updated: 2020-04-22
updated: 2020-11-09
tags:
- server
- linux
@ -10,6 +10,8 @@ tags:
- nixos
---
> 9 Nov 2020: Updated to NixOS 20.09 syntax.
In this post, I show you how I securely configure the NixOS, the server OS behind this website.
This post is Part 2 of a series of articles that show you how I set up Caddy and Tor hidden service on NixOS:
@ -38,13 +40,13 @@ In NixOS, instead of using `useradd` and `passwd` to manage users, you could als
First, I disabled `useradd` and `passwd`.
``` js
``` nix
users.mutableUsers = false;
```
## Disable root
``` js
``` nix
users.root.hashedPassword = "*";
```
@ -52,7 +54,7 @@ users.root.hashedPassword = "*";
User's password can be configured by `users.<name>.password`, obviously this means the password is stored in plain text. Even if you lock down `configuration.nix` with `chmod 600` (which I did), "it is (still) world-readable in the Nix store". The safer way is to store in a hashed form,
``` js
``` nix
users.<name>.hashedPassword = "xxxx";
```
@ -62,7 +64,7 @@ Note that the hash is still world-readable. A more secure option is to use `user
You might be wondering why not just `passwordFile` during installation. The issue is that, in the live CD environment, the "/etc/" folder refers to the live CD's not the actual one which is located in "/mnt/etc/". I mean, you _could_ try "/mnt/etc/nixos/nixos.password", but you gotta remember to update the option after reboot otherwise you would get locked out. "./nixos.password" value doesn't work because `passwordFile` option doesn't support relative path, it must be a full path. Hence, I have use `hashedPassword` during the initial setup and then switch to `passwordFile`. Remember to remove the `hashedPassword` option once you have set up `passwordFile`.
``` js
``` nix
passwordFile = "/etc/nixos/nixos.password";
isNormalUser = true;
extraGroups = [ "wheel" ]; # Enable sudo for the user.
@ -84,7 +86,7 @@ For separation of privilege, each service is launched with different user under
Combining with the previous user configs, I ended up with:
``` js
``` nix
users = {
mutableUsers = false;
@ -144,7 +146,7 @@ $ google-authenticator
Once the secret is generated, TOTP can be enabled using the following config. I configured it to require OTP when login and sudo, in addition to password.
``` js
``` nix
## Requires OTP to login & sudo
security.pam = {
services.login.googleAuthenticator.enable = true;
@ -158,7 +160,7 @@ Since DNS is not encrypted in transit, it risks being tampered. To resolve that,
I use Cloudflare DNS simply because I'm already using its CDN, using other alternatives wouldn't have the privacy benefit since Cloudflare already knows that a visitor is browsing this website though its CDN. Refer to stubby.yml for a full list of supported servers.
``` js
``` nix
## DNS-over-TLS
services.stubby = {
enable = true;
@ -181,7 +183,7 @@ I use Cloudflare DNS simply because I'm already using its CDN, using other alter
Then I point systemd's resolved to stubby. I do configure it to fallback to unencrypted DNS if stubby is not responsive (which does happen). Whether you need an unsecured fallback depends on your cost-benefit. For me, the cost of the site being inaccessible (due to unresponsive stubby) outweighs the benefit of having enforced encryption (my setup is opportunistic).
```
``` nix
networking.nameservers = [ "::1" "127.0.0.1" ];
services.resolved = {
enable = true;
@ -201,7 +203,7 @@ By default, Linux program cannot bind to port <=1024 for security reason. If a p
In my case, I configure iptables to port forward 443 to 4430, so any traffic that hits 443 will be redirected to 4430. Both ports need to be opened, but I do configure my dedicated firewall (separate from the web server) to allow port 443 only.
``` js
``` nix
## Port forwarding
networking.firewall = {
enable = true;
@ -225,7 +227,7 @@ In the config, you can also specify the time that the server will reboot. I reco
(For more advanced usage of `dates`, see [`systemd.time`](https://jlk.fjfi.cvut.cz/arch/manpages/man/systemd.time.7#CALENDAR_EVENTS))
``` js
``` nix
system.autoUpgrade = {
enable = true;
allowReboot = true;
@ -239,16 +241,14 @@ In the config, you can also specify the time that the server will reboot. I reco
I use USBGuard utility to allow or deny USB devices. In a virtual server environment, I only need to use the virtualised USB keyboard. Configuration is easy and straightforward. First, I generate a policy (with root privilege) to allow all currently connected devices:
```
# usbguard generate-policy > /var/lib/usbguard/rules.conf
$ sudo usbguard generate-policy > /var/lib/usbguard/rules.conf
```
Then, I just simply enable the service:
``` js
services.usbguard = {
enable = true;
ruleFile = "/var/lib/usbguard/rules.conf";
};
``` nix
# Load "/var/lib/usbguard/rules.conf" by default
services.usbguard.enable = true;
```
Once enabled, any device not whitelisted in the policy will not be accessible.
@ -306,12 +306,12 @@ Kernel compiled with additional security-oriented patch set. [More details](http
_NixOS [defaults](https://nixos.wiki/wiki/Linux_kernel) to the latest LTS kernel_
```
``` nix
# Latest LTS kernel
boot.kernelPackages = pkgs.linuxPackages_hardened;
```
```
``` nix
# Latest kernel
boot.kernelPackages = pkgs.linuxPackages_latest_hardened;
```
@ -320,11 +320,13 @@ _NixOS [defaults](https://nixos.wiki/wiki/Linux_kernel) to the latest LTS kernel
Since my web server has limited disk space, it needs to run [garbage collector](https://nixos.org/nixos/manual/index.html#sec-nix-gc) from time to time.
```
Since [unattended upgrade](#Unattended-upgrade) is executed on 00:00, I delay garbage collection to 01:00 to avoid time conflict. The order doesn't matter, but there should be at least 15 minutes buffer.
``` nix
## Garbage collector
nix.gc = {
automatic = true;
# Every Monday 00:00
dates = "weekly UTC";
# Every Monday 01:00 (UTC)
dates = "Monday 01:00 UTC";
};
```

View File

@ -2,7 +2,7 @@
title: "Setup Caddy as a reverse proxy on NixOS (Part 3: Caddy)"
excerpt: "Part 3: Configure Caddy"
date: 2020-03-14
updated: 2020-09-09
updated: 2020-11-09
tags:
- server
- linux
@ -10,6 +10,8 @@ tags:
- nixos
---
> 9 Nov 2020: Updated to Caddy 2.1 syntax. Refer to {% post_link caddy-upgrade-v2-proxy 'this article' %} for upgrade guide.
In this segment, I show you how I set up this website (mdleom.com) to reverse proxy to curben.netlify.app using Caddy on NixOS (see above diagram). If you're not using NixOS, simply skip to the [Caddyfile](#Caddyfile) section.
This post is Part 2 of a series of articles that show you how I set up Caddy and Tor hidden service on NixOS:
@ -26,11 +28,10 @@ This post is Part 2 of a series of articles that show you how I set up Caddy and
In NixOS, Caddy can be easily configured through "configuration.nix", without even touching a Caddyfile, if you have a rather simple setup. For example, to serve static files from "/var/www/" folder,
``` plain configuration.nix
``` nix configuration.nix
services.caddy = {
enable = true;
email = example@example.com;
agree = true;
config =
''
example.com {
@ -58,7 +59,7 @@ caddy.nix grants `CAP_NET_BIND_SERVICE` capability which is not needed in my use
I created another nix file which is similar to "caddy.nix", but without `CAP_NET_BIND_SERVICE` capability. I also removed Let's Encrypt-related options since I'm using Cloudflare origin certificate. I renamed the `options.services.caddy` to `options.services.caddyProxy` to avoid clash with "caddy.nix". Save the file to "/etc/caddy/caddyProxy.nix" with root as owner. We'll revisit this file in "[configuration.nix](#configuration.nix)" section later in this guide.
``` plain /etc/caddy/caddyProxy.nix
``` nix /etc/caddy/caddyProxy.nix
{ config, lib, pkgs, ... }:
with lib;
@ -75,6 +76,16 @@ in {
description = "Path to Caddyfile";
};
adapter = mkOption {
default = "caddyfile";
example = "nginx";
type = types.str;
description = ''
Name of the config adapter to use.
See https://caddyserver.com/docs/config-adapters for the full list.
'';
};
dataDir = mkOption {
default = "/var/lib/caddyProxy";
type = types.path;
@ -97,32 +108,32 @@ in {
systemd.services.caddyProxy = {
description = "Caddy web server";
after = [ "network-online.target" ];
wants = [ "network-online.target" ]; # systemd-networkd-wait-online.service
wantedBy = [ "multi-user.target" ];
environment = mkIf (versionAtLeast config.system.stateVersion "17.09")
{ CADDYPATH = cfg.dataDir; };
startLimitIntervalSec = 86400;
# 21.03+
# https://github.com/NixOS/nixpkgs/pull/97512
# startLimitBurst = 5;
# startLimitIntervalSec = 14400;
# startLimitBurst = 10;
serviceConfig = {
ExecStart = ''
${cfg.package}/bin/caddy -root=/var/tmp -conf=${cfg.config}
'';
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
ExecStart = "${cfg.package}/bin/caddy run --config ${cfg.config} --adapter ${cfg.adapter}";
ExecReload = "${cfg.package}/bin/caddy reload --config ${cfg.config} --adapter ${cfg.adapter}";
Type = "simple";
User = "caddyProxy";
Group = "caddyProxy";
Restart = "on-failure";
# <= 20.09
StartLimitBurst = 5;
Restart = "on-abnormal";
StartLimitIntervalSec = 14400;
StartLimitBurst = 10;
NoNewPrivileges = true;
LimitNPROC = 64;
LimitNPROC = 512;
LimitNOFILE = 1048576;
PrivateTmp = true;
PrivateDevices = true;
ProtectHome = true;
ProtectSystem = "full";
ReadWriteDirectories = cfg.dataDir;
KillMode = "mixed";
KillSignal = "SIGQUIT";
TimeoutStopSec = "5s";
};
};
@ -130,7 +141,7 @@ in {
home = cfg.dataDir;
createHome = true;
};
users.groups.caddyProxy = {
members = [ "caddyProxy" ];
};
@ -188,7 +199,11 @@ Subsequent configurations (directives) shall be inside the curly braces. Let's s
```
mdleom.com:4430 www.mdleom.com:4430 {
tls /var/lib/caddyProxy/mdleom.com.pem /var/lib/caddyProxy/mdleom.com.key {
clients /var/lib/caddyProxy/origin-pull-ca.pem
protocols tls1.3
client_auth {
mode require_and_verify
trusted_ca_cert_file /var/lib/caddyProxy/origin-pull-ca.pem
}
}
}
```
@ -198,10 +213,8 @@ mdleom.com:4430 www.mdleom.com:4430 {
Connection to www.mdleom.com is redirected to mdleom.com with HTTP 301 status.
```
redir 301 {
if {label1} is www
/ https://mdleom.com{uri}
}
@www host www.mdleom.com
redir @www https://mdleom.com{uri} permanent
```
`{label1}` placeholder refers to the first part of the request hostname, e.g. if hostname is `foo.bar.com`, `{label1}` is foo, `{label2}` is bar and so on.
@ -211,10 +224,8 @@ Connection to www.mdleom.com is redirected to mdleom.com with HTTP 301 status.
If you prefer to redirect apex to www,
```
redir 301 {
if {label1} is mdleom
/ https://www.mdleom.com{uri}
}
@www host mdleom.com
redir @www https://www.mdleom.com{uri} permanent
```
### Reverse proxy
@ -229,103 +240,104 @@ Aside from reverse proxy to curben.netlify.app, I also configured my Netlify web
In Caddyfile, the config can be expressed as:
``` plain
proxy /img https://cdn.statically.io/img/gitlab.com/curben/blog/raw/site {
without /img
handle_path /img/* {
rewrite * /img/gitlab.com/curben/blog/raw/site{path}
reverse_proxy https://cdn.statically.io
}
rewrite /screenshot {
r (.*)
to /screenshot{1}?mobile=true
handle_path /screenshot/* {
rewrite * /screenshot/curben.netlify.app{path}?mobile=true
reverse_proxy https://cdn.statically.io
}
proxy /screenshot https://cdn.statically.io/screenshot/curben.netlify.app {
without /screenshot
}
proxy / https://curben.netlify.app
reverse_proxy https://curben.netlify.app
```
`without` directive is necessary to remove `libs/` from the path, so that "mdleom.com/libs/foo/bar.js" is linked to "https://cdn.statically.io/libs/foo/bar.js", not "https://cdn.statically.io/libs/libs/foo/bar.js".
For `/screenshot`, since the `proxy` doesn't support variable like the Netlify `:splat`, to prepend "?mobile=true" to the link in the background (without using 301 redirection), I use `rewrite` directive which has a regex match function. I use the regex to capture the path after `screenshot` and call it using `{1}`.
`rewrite` directive is necessary to remove `img/` and `screenshot/*` from the path, so that "mdleom.com/img/foo.jpg" is linked to "https://cdn.statically.io/img/foo.jpg", not "https://cdn.statically.io/img/img/foo.jpg".
### Host header
To make sure Caddy sends the correct `Host:` header to the upstream/backend locations, I use `header_upstream` option,
``` plain
proxy /img https://cdn.statically.io/img/gitlab.com/curben/blog/raw/site {
without /img
header_upstream Host cdn.statically.io
{% codeblock mark:5,13,18 %}
handle_path /img/* {
rewrite * /img/gitlab.com/curben/blog/raw/site{path}
reverse_proxy https://cdn.statically.io {
header_up Host cdn.statically.io
}
}
rewrite /screenshot {
r (.*)
to /screenshot{1}?mobile=true
handle_path /screenshot/* {
rewrite * /screenshot/curben.netlify.app{path}?mobile=true
reverse_proxy https://cdn.statically.io {
header_up Host cdn.statically.io
}
}
proxy /screenshot https://cdn.statically.io/screenshot/curben.netlify.app {
without /screenshot
header_upstream Host cdn.statically.io
reverse_proxy https://curben.netlify.app {
header_up Host curben.netlify.app
}
proxy / https://curben.netlify.app {
header_upstream Host cdn.statically.io
}
```
There are a few repetitions for rewriting the header for Statically. I can group that option as a global variable and call it using `import`.
```
(staticallyCfg) {
header_upstream Host cdn.statically.io
}
mdleom.com {
proxy /img ... {
import staticallyCfg
}
proxy /screenshot ... {
import staticallyCfg
}
}
```
{% endcodeblock %}
### Add or remove headers
To prevent any unnecessary request headers from being sent to the upstreams, I use `header_upstream`. I use it to remove cookie, referer and [other headers](https://support.cloudflare.com/hc/en-us/articles/200170986-How-does-Cloudflare-handle-HTTP-Request-headers-) added by Cloudflare. Since there are many headers to remove, I group them as a global variable. I apply it to all `proxy` directive.
To prevent any unnecessary request headers from being sent to the upstreams, I use `header_up`. I use it to remove cookie, referer and [other headers](https://support.cloudflare.com/hc/en-us/articles/200170986-How-does-Cloudflare-handle-HTTP-Request-headers-) added by Cloudflare. Since there are many headers to remove, I group them as a global variable. I apply it to all `reverse_proxy` directives.
```
{% codeblock mark:25,34,40 %}
(removeHeaders) {
header_upstream -cookie
header_upstream -referer
# Remove Cloudflare headers
# https://support.cloudflare.com/hc/en-us/articles/200170986-How-does-Cloudflare-handle-HTTP-Request-headers-
header_upstream -cf-ipcountry
header_upstream -cf-connecting-ip
header_upstream -x-forwarded-for
header_upstream -x-forwarded-proto
header_upstream -cf-ray
header_upstream -cf-visitor
header_upstream -true-client-ip
header_upstream -cdn-loop
header_upstream -cf-request-id
header_upstream -cf-cache-status
header_up -cdn-loop
header_up -cf-cache-status
header_up -cf-connecting-ip
header_up -cf-ipcountry
header_up -cf-ray
header_up -cf-request-id
header_up -cf-visitor
header_up -cookie
header_up -referer
header_up -sec-ch-ua
header_up -sec-ch-ua-mobile
header_up -true-client-ip
header_up -via
header_up -x-forwarded-for
header_up -x-forwarded-proto
header_up User-Agent "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
}
mdleom.com {
proxy /img ... {
handle_path /img/* {
rewrite * /img/gitlab.com/curben/blog/raw/site{path}
reverse_proxy https://cdn.statically.io {
import removeHeaders
header_up Host cdn.statically.io
}
}
handle_path /screenshot/* {
rewrite * /screenshot/curben.netlify.app{path}?mobile=true
reverse_proxy https://cdn.statically.io {
import removeHeaders
header_up Host cdn.statically.io
}
}
reverse_proxy https://curben.netlify.app {
import removeHeaders
header_up Host curben.netlify.app
}
}
```
{% endcodeblock %}
The upstream locations insert some information into the response headers that are irrelevant to the site visitors. I use `header` directive to filter them out. It applies to all `proxy` directive.
The upstream locations insert some information into the response headers that are irrelevant to the site visitors. I use `header` directive to filter them out. It also applies to all `reverse_proxy` directives.
```
header / {
-server
header {
-access-control-allow-origin
-access-control-expose-headers
-alt-svc
-cdn-cache
-cdn-cachedat
@ -334,121 +346,209 @@ The upstream locations insert some information into the response headers that ar
-cdn-requestcountrycode
-cdn-requestid
-cdn-uid
-cf-bgj
-cf-cache-status
-cf-polished
-cf-ray
-cf-request-id
-content-disposition
-etag
-expect-ct
-server
-set-cookie
-timing-allow-origin
-via
-x-bytes-saved
-x-cache
-x-cache-hits
-x-nf-request-id
-x-served-by
Cache-Control "max-age=604800, public"
-x-timer
Clear-Site-Data `"cookies", "storage"`
Content-Language "en-GB"
Content-Security-Policy "default-src 'self'; child-src 'none'; connect-src 'none'; font-src 'none'; frame-src 'none'; img-src 'self'; manifest-src 'none'; media-src 'none'; object-src 'none'; prefetch-src 'none'; script-src 'self'; style-src 'self'; worker-src 'none'; base-uri 'none'; form-action https://duckduckgo.com https://3g2upl4pq6kufc4m.onion; frame-ancestors 'none'; block-all-mixed-content"
Expires "0"
Feature-Policy "accelerometer 'none'; ambient-light-sensor 'none'; autoplay 'none'; camera 'none'; display-capture 'none'; document-domain 'none'; encrypted-media 'none'; fullscreen 'none'; geolocation 'none'; gyroscope 'none'; magnetometer 'none'; microphone 'none'; midi 'none'; payment 'none'; picture-in-picture 'none'; speaker 'none'; sync-xhr 'none'; usb 'none'; vibrate 'none'; vr 'none'; wake-lock 'none'; webauthn 'none'; xr-spatial-tracking 'none'"
Referrer-Policy "no-referrer"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
X-XSS-Protection "1; mode=block"
defer
}
```
I also add the `Cache-Control` and `Referrer-Policy` to the response header. Use minus (-) sign before each option to remove particular header. Without minus sign, the specified header is either added or replacing an existing one.
### header and header_downstream
### Cache-Control
`/libs` folder contains third-party libraries. Since the library is usually requested by a specific version, we can safely assume that the response would remain the same. This means I can set long expiration and `immutable` on the response. [`immutable`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control#Revalidation_and_reloading) is to tell the browser that revalidation is not needed.
```
header / {
Cache-Control "max-age=604800, public"
header {
Cache-Control "max-age=86400, public"
}
header /libs {
header /libs/* {
Cache-Control "public, max-age=31536000, immutable"
}
```
### Complete Caddyfile
``` plain Caddyfile
(removeHeaders) {
header_upstream -cookie
header_upstream -referer
# Remove Cloudflare headers
# https://support.cloudflare.com/hc/en-us/articles/200170986-How-does-Cloudflare-handle-HTTP-Request-headers-
header_upstream -cf-ipcountry
header_upstream -cf-connecting-ip
header_upstream -x-forwarded-for
header_upstream -x-forwarded-proto
header_upstream -cf-ray
header_upstream -cf-visitor
header_upstream -true-client-ip
header_upstream -cdn-loop
header_upstream -cf-request-id
header_upstream -cf-cache-status
Since I also set up reverse proxy for {% post_link tor-hidden-onion-nixos 'Tor Onion' %} and {% post_link i2p-eepsite-nixos 'I2P Eepsite' %}, I refactor most of the configuration into "common.conf" and import it into "caddyProxy.conf".
``` plain common.conf
## Optional: disable admin endpoint and http->https redirect
#{
# admin off
# auto_https disable_redirects
#}
(setHeaders) {
-access-control-allow-origin
-access-control-expose-headers
-alt-svc
-cdn-cache
-cdn-cachedat
-cdn-edgestorageid
-cdn-pullzone
-cdn-requestcountrycode
-cdn-requestid
-cdn-uid
-cf-bgj
-cf-cache-status
-cf-polished
-cf-ray
-cf-request-id
-content-disposition
-etag
-expect-ct
-server
-set-cookie
-timing-allow-origin
-via
-x-bytes-saved
-x-cache
-x-cache-hits
-x-nf-request-id
-x-served-by
-x-timer
Cache-Control "max-age=86400, public"
Clear-Site-Data `"cookies", "storage"`
Content-Language "en-GB"
Content-Security-Policy "default-src 'self'; child-src 'none'; connect-src 'none'; font-src 'none'; frame-src 'none'; img-src 'self'; manifest-src 'none'; media-src 'none'; object-src 'none'; prefetch-src 'none'; script-src 'self'; style-src 'self'; worker-src 'none'; base-uri 'none'; form-action https://duckduckgo.com https://3g2upl4pq6kufc4m.onion; frame-ancestors 'none'; block-all-mixed-content"
Expires "0"
Feature-Policy "accelerometer 'none'; ambient-light-sensor 'none'; autoplay 'none'; camera 'none'; display-capture 'none'; document-domain 'none'; encrypted-media 'none'; fullscreen 'none'; geolocation 'none'; gyroscope 'none'; magnetometer 'none'; microphone 'none'; midi 'none'; payment 'none'; picture-in-picture 'none'; speaker 'none'; sync-xhr 'none'; usb 'none'; vibrate 'none'; vr 'none'; wake-lock 'none'; webauthn 'none'; xr-spatial-tracking 'none'"
Referrer-Policy "no-referrer"
X-Content-Type-Options "nosniff"
X-Frame-Options "DENY"
X-XSS-Protection "1; mode=block"
}
(staticallyCfg) {
header_downstream Strict-Transport-Security "max-age=31536000"
header_upstream Host cdn.statically.io
(removeHeaders) {
header_up -cdn-loop
header_up -cf-cache-status
header_up -cf-connecting-ip
header_up -cf-ipcountry
header_up -cf-ray
header_up -cf-request-id
header_up -cf-visitor
header_up -cookie
header_up -referer
header_up -sec-ch-ua
header_up -sec-ch-ua-mobile
header_up -true-client-ip
header_up -via
header_up -x-forwarded-for
header_up -x-forwarded-proto
header_up User-Agent "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
}
(oneWeekCache) {
Cache-Control "max-age=604800, public"
}
(pathProxy) {
header /js/* {
import oneWeekCache
defer
}
header /css/* {
import oneWeekCache
defer
}
header /svg/* {
import oneWeekCache
defer
}
header /libs/* {
Cache-Control "max-age=31536000, public, immutable"
defer
}
handle_path /img/* {
rewrite * /img/gitlab.com/curben/blog/raw/site{path}
reverse_proxy https://cdn.statically.io {
import removeHeaders
header_up Host cdn.statically.io
}
}
header /img/* {
import oneWeekCache
defer
}
handle_path /screenshot/* {
rewrite * /screenshot/curben.netlify.app{path}?mobile=true
reverse_proxy https://cdn.statically.io {
import removeHeaders
header_up Host cdn.statically.io
}
}
header /screenshot/* {
import oneWeekCache
defer
}
reverse_proxy https://curben.netlify.app {
import removeHeaders
header_up Host curben.netlify.app
}
}
```
``` plain caddyProxy.conf
import common.conf
## mdleom.com
mdleom.com:4430 www.mdleom.com:4430 {
tls /var/lib/caddyProxy/mdleom.com.pem /var/lib/caddyProxy/mdleom.com.key {
clients /var/lib/caddyProxy/origin-pull-ca.pem
protocols tls1.3
client_auth {
mode require_and_verify
trusted_ca_cert_file /var/lib/caddyProxy/origin-pull-ca.pem
}
}
# www -> apex
redir 301 {
if {label1} is www
/ https://mdleom.com{uri}
@www host www.mdleom.com
redir @www https://mdleom.com{uri} permanent
header {
import setHeaders
Onion-Location "http://xw226dvxac7jzcpsf4xb64r4epr6o5hgn46dxlqk7gnjptakik6xnzqd.onion"
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
defer
}
header / {
-server
-alt-svc
-cdn-cache
-cdn-cachedat
-cdn-edgestorageid
-cdn-pullzone
-cdn-requestcountrycode
-cdn-requestid
-cdn-uid
-cf-cache-status
-cf-ray
-cf-request-id
-etag
-set-cookie
-x-bytes-saved
-x-cache
-x-nf-request-id
-x-served-by
Cache-Control "max-age=604800, public"
Referrer-Policy "no-referrer"
}
header /libs {
Cache-Control "public, max-age=31536000, immutable"
}
proxy /img https://cdn.statically.io/img/gitlab.com/curben/blog/raw/site {
without /img
import removeHeaders
import staticallyCfg
}
rewrite /screenshot {
r (.*)
to /screenshot{1}?mobile=true
}
proxy /screenshot https://cdn.statically.io/screenshot/curben.netlify.app {
without /screenshot
import removeHeaders
import staticallyCfg
}
proxy / https://curben.netlify.app {
import removeHeaders
header_upstream Host curben.netlify.app
}
import pathProxy
}
```
@ -456,7 +556,7 @@ mdleom.com:4430 www.mdleom.com:4430 {
One last thing to do is to import "[caddyProxy.nix](#caddyProxy.nix)" and enable `services.caddyProxy`.
``` js /etc/nixos/configuration.nix
``` nix /etc/nixos/configuration.nix
require = [ /etc/caddy/caddyProxy.nix ];
services.caddyProxy = {
enable = true;

View File

@ -2,7 +2,7 @@
title: "How to make your website available over I2P Eepsite on NixOS"
excerpt: "A guide on I2P Eepsite on NixOS"
date: 2020-03-21
updated: 2020-09-09
updated: 2020-11-09
tags:
- server
- linux
@ -12,6 +12,8 @@ tags:
- censorship
---
> 9 Nov 2020: Updated to Caddy 2.1 syntax. Refer to {% post_link caddy-upgrade-v2-proxy 'this article' %} for upgrade guide.
In this segment, I show you how I set up I2P Eepsite service that reverse proxy to curben.netlify.app. This website can be accessed using this [B32 address](http://ggucqf2jmtfxcw7us5sts3x7u2qljseocfzlhzebfpihkyvhcqfa.b32.i2p) or [mdleom.i2p](http://mdleom.i2p/)
This post is Part 5 of a series of articles that show you how I set up Caddy, Tor hidden service and I2P Eepsite on NixOS:
@ -123,6 +125,16 @@ in {
description = "Path to Caddyfile";
};
adapter = mkOption {
default = "caddyfile";
example = "nginx";
type = types.str;
description = ''
Name of the config adapter to use.
See https://caddyserver.com/docs/config-adapters for the full list.
'';
};
dataDir = mkOption {
default = "/var/lib/caddyI2p";
type = types.path;
@ -145,40 +157,40 @@ in {
systemd.services.caddyI2p = {
description = "Caddy web server";
after = [ "network-online.target" ];
wants = [ "network-online.target" ]; # systemd-networkd-wait-online.service
wantedBy = [ "multi-user.target" ];
environment = mkIf (versionAtLeast config.system.stateVersion "17.09")
{ CADDYPATH = cfg.dataDir; };
startLimitIntervalSec = 86400;
# 21.03+
# https://github.com/NixOS/nixpkgs/pull/97512
# startLimitBurst = 5;
# startLimitIntervalSec = 14400;
# startLimitBurst = 10;
serviceConfig = {
ExecStart = ''
${cfg.package}/bin/caddy -root=/var/tmp -conf=${cfg.config}
'';
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
ExecStart = "${cfg.package}/bin/caddy run --config ${cfg.config} --adapter ${cfg.adapter}";
ExecReload = "${cfg.package}/bin/caddy reload --config ${cfg.config} --adapter ${cfg.adapter}";
Type = "simple";
User = "caddyProxy";
Group = "caddyProxy";
Restart = "on-failure";
# <= 20.09
StartLimitBurst = 5;
User = "caddyI2p";
Group = "caddyI2p";
Restart = "on-abnormal";
StartLimitIntervalSec = 14400;
StartLimitBurst = 10;
NoNewPrivileges = true;
LimitNPROC = 64;
LimitNPROC = 512;
LimitNOFILE = 1048576;
PrivateTmp = true;
PrivateDevices = true;
ProtectHome = true;
ProtectSystem = "full";
ReadWriteDirectories = cfg.dataDir;
KillMode = "mixed";
KillSignal = "SIGQUIT";
TimeoutStopSec = "5s";
};
};
users.users.caddyI2p = {
home = cfg.dataDir;
createHome = true;
};
users.groups.caddyI2p = {
members = [ "caddyI2p" ];
};
@ -188,11 +200,13 @@ in {
### File ownership and permissions
After you save the file to **/etc/caddy/caddyI2p.nix**, remember to restrict it to root.
After you save the file to **/etc/caddy/caddyI2p.nix**, remember to restrict it to `caddyI2p` user.
```
# chown root:root /etc/caddy/caddyI2p.nix
# chown 600 /etc/caddy/caddyI2p.nix
$ chown caddyI2p:caddyI2p /etc/caddy/caddyI2p.nix
$ chown 600 /etc/caddy/caddyI2p.nix
# "common.conf" must be readable by other users
$ chmod o+r /etc/caddy/common.conf
```
## caddyFile
@ -200,97 +214,36 @@ After you save the file to **/etc/caddy/caddyI2p.nix**, remember to restrict it
Create a new caddyFile in `/etc/caddy/caddyI2p.conf` and starts with the following config:
```
ggucqf2jmtfxcw7us5sts3x7u2qljseocfzlhzebfpihkyvhcqfa.b32.i2p:8081 mdleom.i2p:8081 {
http://ggucqf2jmtfxcw7us5sts3x7u2qljseocfzlhzebfpihkyvhcqfa.b32.i2p:8081 http://mdleom.i2p:8081 {
bind ::1
tls off
header / {
header {
-strict-transport-security
defer
}
}
```
Update the B32 address as per the value derived from the [previous section](#B32-address). `mdleom.i2p` is my I2P domain that I registered with a jump service like [stats.i2p](http://stats.i2p/) and it acts as a shortcut to my B32 address. `tls` (HTTPS) is disabled here because it's not necessary as Tor hidden service already encrypts the traffic. Let's Encrypt doesn't support validating a .i2p address. Since HTTPS is not enabled, `strict-transport-security` (HSTS) no longer applies and the header needs to be removed to prevent the browser from attempting to connect to `https://`. It binds to loopback so it only listens to localhost.
Update the B32 address as per the value derived from the [previous section](#B32-address). `mdleom.i2p` is my I2P domain that I registered with a jump service like [stats.i2p](http://stats.i2p/) and it acts as a shortcut to my B32 address. HTTPS is disabled by specifying `http://` prefix, HTTPS is not necessary as Eepsite already encrypts the traffic. Let's Encrypt doesn't support validating a .i2p address. Since HTTPS is not enabled, `strict-transport-security` (HSTS) no longer applies and the header needs to be removed to prevent the browser from attempting to connect to `https://`. It binds to IPv6 loopback so it only listens to localhost, specify `bind 127.0.0.1 ::1` if you need IPv4.
The rest are similar to "[caddyTor.conf](/blog/2020/03/16/tor-hidden-onion-nixos/#caddyTor.conf)" and "[caddyProxy.conf](/blog/2020/03/14/caddy-nix-part-3/#caddyFile)".
The rest are similar to "[caddyTor.conf](/blog/2020/03/16/tor-hidden-onion-nixos/#caddyTor.conf)" and "[caddyProxy.conf](/blog/2020/03/14/caddy-nix-part-3/#Complete-Caddyfile)". Content of "common.conf" is available at [this section](/blog/2020/03/14/caddy-nix-part-3/#Complete-Caddyfile).
``` plain /etc/caddy/caddyI2p.conf
(removeHeaders) {
header_upstream -cookie
header_upstream -referer
header_upstream -cf-ipcountry
header_upstream -cf-connecting-ip
header_upstream -x-forwarded-for
header_upstream -x-forwarded-proto
header_upstream -cf-ray
header_upstream -cf-visitor
header_upstream -true-client-ip
header_upstream -cdn-loop
header_upstream -cf-request-id
header_upstream -cf-cache-status
}
(staticallyCfg) {
header_upstream Host cdn.statically.io
}
import common.conf
# I2P Eepsite
ggucqf2jmtfxcw7us5sts3x7u2qljseocfzlhzebfpihkyvhcqfa.b32.i2p:8081 mdleom.i2p:8081 {
http://ggucqf2jmtfxcw7us5sts3x7u2qljseocfzlhzebfpihkyvhcqfa.b32.i2p:8081 http://mdleom.i2p:8081 {
bind ::1
tls off
header / {
-server
-alt-svc
-cdn-cache
-cdn-cachedat
-cdn-edgestorageid
-cdn-pullzone
-cdn-requestcountrycode
-cdn-requestid
-cdn-uid
-cf-cache-status
-cf-ray
-cf-request-id
-etag
-set-cookie
-strict-transport-security
-x-bytes-saved
-x-cache
-x-nf-request-id
-x-served-by
Cache-Control "max-age=604800, public"
Referrer-Policy "no-referrer"
header {
import setHeaders
-strict-transport-origin
defer
}
header /libs {
Cache-Control "public, max-age=31536000, immutable"
}
proxy /img https://cdn.statically.io/img/gitlab.com/curben/blog/raw/site {
without /img
import removeHeaders
import staticallyCfg
}
rewrite /screenshot {
r (.*)
to /screenshot{1}?mobile=true
}
proxy /screenshot https://cdn.statically.io/screenshot/curben.netlify.app {
without /screenshot
import removeHeaders
import staticallyCfg
}
proxy / https://curben.netlify.app {
import removeHeaders
header_upstream Host curben.netlify.app
}
import pathProxy
}
```
### Alternate Caddyfile
@ -299,17 +252,16 @@ There is another approach which is suitable if you have a website that you don't
```
# Do not use this approach unless you are absolutely sure
ggucqf2jmtfxcw7us5sts3x7u2qljseocfzlhzebfpihkyvhcqfa.b32.i2p:8081 mdleom.i2p:8081 {
http://ggucqf2jmtfxcw7us5sts3x7u2qljseocfzlhzebfpihkyvhcqfa.b32.i2p:8081 http://mdleom.i2p:8081 {
bind ::1
tls off
header / {
header {
-strict-transport-security
defer
}
proxy / https://mdleom.com {
header_upstream Host mdleom.com
reverse_proxy https://mdleom.com {
header_up Host mdleom.com
}
}
```
@ -318,7 +270,7 @@ ggucqf2jmtfxcw7us5sts3x7u2qljseocfzlhzebfpihkyvhcqfa.b32.i2p:8081 mdleom.i2p:808
Start the Caddy service.
``` js /etc/nixos/configuration.nix
``` nix /etc/nixos/configuration.nix
require = [ /etc/caddy/caddyProxy.nix /etc/caddy/caddyTor.nix /etc/caddy/caddyI2p.nix ];
services.caddyI2p = {
enable = true;

View File

@ -2,7 +2,7 @@
title: "How to make your website available over Tor hidden service on NixOS"
excerpt: "A guide on Tor hidden service on NixOS"
date: 2020-03-16
updated: 2020-09-09
updated: 2020-11-09
tags:
- server
- linux
@ -12,6 +12,8 @@ tags:
- censorship
---
> 9 Nov 2020: Updated to Caddy 2.1 syntax. Refer to {% post_link caddy-upgrade-v2-proxy 'this article' %} for upgrade guide.
In this segment, I show you how I set up Tor hidden (.onion) service that reverse proxy to curben.netlify.app. This website can be accessed through the following [.onion address](http://xw226dvxac7jzcpsf4xb64r4epr6o5hgn46dxlqk7gnjptakik6xnzqd.onion).
This post is Part 4 of a series of articles that show you how I set up Caddy, Tor hidden service and I2P Eepsite on NixOS:
@ -32,7 +34,7 @@ Note that this only applies to the traffic between visitor and the (Caddy) web s
The first step is to bring up a Tor hidden service to get an onion address. Add the following options to **configuration.nix**:
``` plain /etc/nixos/configuration.nix
``` nix /etc/nixos/configuration.nix
## Tor onion
services.tor = {
enable = true;
@ -87,19 +89,29 @@ I set up another Caddy-powered reverse proxy which is separate from the {% post_
with lib;
let
cfg = config.services.caddyTor;
cfg = config.services.caddyProxy;
in {
options.services.caddyTor = {
options.services.caddyProxy = {
enable = mkEnableOption "Caddy web server";
config = mkOption {
default = "/etc/caddy/caddyTor.conf";
default = "/etc/caddy/caddyProxy.conf";
type = types.str;
description = "Path to Caddyfile";
};
adapter = mkOption {
default = "caddyfile";
example = "nginx";
type = types.str;
description = ''
Name of the config adapter to use.
See https://caddyserver.com/docs/config-adapters for the full list.
'';
};
dataDir = mkOption {
default = "/var/lib/caddyTor";
default = "/var/lib/caddyProxy";
type = types.path;
description = ''
The data directory, for storing certificates. Before 17.09, this
@ -117,45 +129,45 @@ in {
};
config = mkIf cfg.enable {
systemd.services.caddyTor = {
systemd.services.caddyProxy = {
description = "Caddy web server";
after = [ "network-online.target" ];
wants = [ "network-online.target" ]; # systemd-networkd-wait-online.service
wantedBy = [ "multi-user.target" ];
environment = mkIf (versionAtLeast config.system.stateVersion "17.09")
{ CADDYPATH = cfg.dataDir; };
startLimitIntervalSec = 86400;
# 21.03+
# https://github.com/NixOS/nixpkgs/pull/97512
# startLimitBurst = 5;
# startLimitIntervalSec = 14400;
# startLimitBurst = 10;
serviceConfig = {
ExecStart = ''
${cfg.package}/bin/caddy -root=/var/tmp -conf=${cfg.config}
'';
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
ExecStart = "${cfg.package}/bin/caddy run --config ${cfg.config} --adapter ${cfg.adapter}";
ExecReload = "${cfg.package}/bin/caddy reload --config ${cfg.config} --adapter ${cfg.adapter}";
Type = "simple";
User = "caddyProxy";
Group = "caddyProxy";
Restart = "on-failure";
# <= 20.09
StartLimitBurst = 5;
Restart = "on-abnormal";
StartLimitIntervalSec = 14400;
StartLimitBurst = 10;
NoNewPrivileges = true;
LimitNPROC = 64;
LimitNPROC = 512;
LimitNOFILE = 1048576;
PrivateTmp = true;
PrivateDevices = true;
ProtectHome = true;
ProtectSystem = "full";
ReadWriteDirectories = cfg.dataDir;
KillMode = "mixed";
KillSignal = "SIGQUIT";
TimeoutStopSec = "5s";
};
};
users.users.caddyTor = {
users.users.caddyProxy = {
home = cfg.dataDir;
createHome = true;
};
users.groups.caddyTor = {
members = [ "caddyTor" ];
users.groups.caddyProxy = {
members = [ "caddyProxy" ];
};
};
}
@ -175,96 +187,40 @@ After you save the file to **/etc/caddy/CaddyTor.nix**, remember to restrict it
Create a new caddyFile in `/etc/caddy/caddyTor.conf` and starts with the following config:
```
xw226dvxac7jzcpsf4xb64r4epr6o5hgn46dxlqk7gnjptakik6xnzqd.onion:8080 {
import common.conf
# Tor onion
http://xw226dvxac7jzcpsf4xb64r4epr6o5hgn46dxlqk7gnjptakik6xnzqd.onion:8080 {
bind ::1
tls off
header / {
-strict-transport-security
header {
import setHeaders
-strict-transport-origin
defer
}
import pathProxy
}
```
Update the onion address to the value shown in "[/var/lib/tor/onion/myOnion/hostname](#configuration.nix)". `tls` (HTTPS) is disabled here because it's not necessary as Tor hidden service already encrypts the traffic. Let's Encrypt doesn't support validating a .onion address. The only way is to purchase the cert from [Digicert](https://www.digicert.com/blog/ordering-a-onion-certificate-from-digicert/). Since HTTPS is not enabled, `strict-transport-security` (HSTS) no longer applies and the header needs to be removed to prevent the browser from attempting to connect to `https://`. It binds to loopback so it only listens to localhost.
Update the onion address to the value shown in "[/var/lib/tor/onion/myOnion/hostname](#configuration.nix)". HTTPS is disabled by specifying `http://` prefix, HTTPS is not necessary as Tor hidden service already encrypts the traffic. Let's Encrypt doesn't support validating a .onion address. The only way is to purchase the cert from [Digicert](https://www.digicert.com/blog/ordering-a-onion-certificate-from-digicert/). Since HTTPS is not enabled, `strict-transport-security` (HSTS) no longer applies and the header needs to be removed to prevent the browser from attempting to connect to `https://`. It binds to IPv6 loopback so it only listens to localhost, specify `bind 127.0.0.1 ::1` if you need IPv4.
The rest are similar to "[caddyProxy.conf](/blog/2020/03/14/caddy-nix-part-3/#caddyFile)".
The rest are similar to "[caddyProxy.conf](blog/2020/03/14/caddy-nix-part-3/#Complete-Caddyfile)". Content of "common.conf" is available at [this section](/blog/2020/03/14/caddy-nix-part-3/#Complete-Caddyfile).
``` plain /etc/caddy/caddyTor.conf
(removeHeaders) {
header_upstream -cookie
header_upstream -referer
header_upstream -cf-ipcountry
header_upstream -cf-connecting-ip
header_upstream -x-forwarded-for
header_upstream -x-forwarded-proto
header_upstream -cf-ray
header_upstream -cf-visitor
header_upstream -true-client-ip
header_upstream -cdn-loop
header_upstream -cf-request-id
header_upstream -cf-cache-status
}
(staticallyCfg) {
header_upstream Host cdn.statically.io
}
import common.conf
# Tor onion
xw226dvxac7jzcpsf4xb64r4epr6o5hgn46dxlqk7gnjptakik6xnzqd.onion:8080 {
http://xw226dvxac7jzcpsf4xb64r4epr6o5hgn46dxlqk7gnjptakik6xnzqd.onion:8080 {
bind ::1
tls off
header / {
-server
-alt-svc
-cdn-cache
-cdn-cachedat
-cdn-edgestorageid
-cdn-pullzone
-cdn-requestcountrycode
-cdn-requestid
-cdn-uid
-cf-cache-status
-cf-ray
-cf-request-id
-etag
-set-cookie
-strict-transport-security
-x-bytes-saved
-x-cache
-x-nf-request-id
-x-served-by
Cache-Control "max-age=604800, public"
Referrer-Policy "no-referrer"
header {
import setHeaders
-strict-transport-origin
defer
}
header /libs {
Cache-Control "public, max-age=31536000, immutable"
}
proxy /img https://cdn.statically.io/img/gitlab.com/curben/blog/raw/site {
without /img
import removeHeaders
import staticallyCfg
}
rewrite /screenshot {
r (.*)
to /screenshot{1}?mobile=true
}
proxy /screenshot https://cdn.statically.io/screenshot/curben.netlify.app {
without /screenshot
import removeHeaders
import staticallyCfg
}
proxy / https://curben.netlify.app {
import removeHeaders
header_upstream Host curben.netlify.app
}
import pathProxy
}
```
@ -276,17 +232,16 @@ This is also suitable if you have a website that you can't root access.
```
# Do not use this approach unless you are absolutely sure
xw226dvxac7jzcpsf4xb64r4epr6o5hgn46dxlqk7gnjptakik6xnzqd.onion:8080 {
http://xw226dvxac7jzcpsf4xb64r4epr6o5hgn46dxlqk7gnjptakik6xnzqd.onion:8080 {
bind ::1
tls off
header / {
header {
-strict-transport-security
defer
}
proxy / https://mdleom.com {
header_upstream Host mdleom.com
reverse_proxy https://mdleom.com {
header_up Host mdleom.com
}
}
```