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). 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, ... }: { config, pkgs, ... }:
{ {

View File

@ -2,7 +2,7 @@
title: "Setup Caddy as a reverse proxy on NixOS (Part 2: Hardening)" title: "Setup Caddy as a reverse proxy on NixOS (Part 2: Hardening)"
excerpt: "Part 2: Securing NixOS" excerpt: "Part 2: Securing NixOS"
date: 2020-03-04 date: 2020-03-04
updated: 2020-04-22 updated: 2020-11-09
tags: tags:
- server - server
- linux - linux
@ -10,6 +10,8 @@ tags:
- nixos - 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. 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: 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`. First, I disabled `useradd` and `passwd`.
``` js ``` nix
users.mutableUsers = false; users.mutableUsers = false;
``` ```
## Disable root ## Disable root
``` js ``` nix
users.root.hashedPassword = "*"; 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, 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"; 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`. 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"; passwordFile = "/etc/nixos/nixos.password";
isNormalUser = true; isNormalUser = true;
extraGroups = [ "wheel" ]; # Enable sudo for the user. 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: Combining with the previous user configs, I ended up with:
``` js ``` nix
users = { users = {
mutableUsers = false; 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. 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 ## Requires OTP to login & sudo
security.pam = { security.pam = {
services.login.googleAuthenticator.enable = true; 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. 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 ## DNS-over-TLS
services.stubby = { services.stubby = {
enable = true; 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). 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" ]; networking.nameservers = [ "::1" "127.0.0.1" ];
services.resolved = { services.resolved = {
enable = true; 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. 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 ## Port forwarding
networking.firewall = { networking.firewall = {
enable = true; 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)) (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 = { system.autoUpgrade = {
enable = true; enable = true;
allowReboot = 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: 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: Then, I just simply enable the service:
``` js ``` nix
services.usbguard = { # Load "/var/lib/usbguard/rules.conf" by default
enable = true; services.usbguard.enable = true;
ruleFile = "/var/lib/usbguard/rules.conf";
};
``` ```
Once enabled, any device not whitelisted in the policy will not be accessible. 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_ _NixOS [defaults](https://nixos.wiki/wiki/Linux_kernel) to the latest LTS kernel_
``` ``` nix
# Latest LTS kernel # Latest LTS kernel
boot.kernelPackages = pkgs.linuxPackages_hardened; boot.kernelPackages = pkgs.linuxPackages_hardened;
``` ```
``` ``` nix
# Latest kernel # Latest kernel
boot.kernelPackages = pkgs.linuxPackages_latest_hardened; 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 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 ## Garbage collector
nix.gc = { nix.gc = {
automatic = true; automatic = true;
# Every Monday 00:00 # Every Monday 01:00 (UTC)
dates = "weekly UTC"; dates = "Monday 01:00 UTC";
}; };
``` ```

View File

@ -2,7 +2,7 @@
title: "Setup Caddy as a reverse proxy on NixOS (Part 3: Caddy)" title: "Setup Caddy as a reverse proxy on NixOS (Part 3: Caddy)"
excerpt: "Part 3: Configure Caddy" excerpt: "Part 3: Configure Caddy"
date: 2020-03-14 date: 2020-03-14
updated: 2020-09-09 updated: 2020-11-09
tags: tags:
- server - server
- linux - linux
@ -10,6 +10,8 @@ tags:
- nixos - 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. 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: 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, 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 = { services.caddy = {
enable = true; enable = true;
email = example@example.com; email = example@example.com;
agree = true;
config = config =
'' ''
example.com { 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. 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, ... }: { config, lib, pkgs, ... }:
with lib; with lib;
@ -75,6 +76,16 @@ in {
description = "Path to Caddyfile"; 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 { dataDir = mkOption {
default = "/var/lib/caddyProxy"; default = "/var/lib/caddyProxy";
type = types.path; type = types.path;
@ -97,32 +108,32 @@ in {
systemd.services.caddyProxy = { systemd.services.caddyProxy = {
description = "Caddy web server"; description = "Caddy web server";
after = [ "network-online.target" ]; after = [ "network-online.target" ];
wants = [ "network-online.target" ]; # systemd-networkd-wait-online.service
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
environment = mkIf (versionAtLeast config.system.stateVersion "17.09")
{ CADDYPATH = cfg.dataDir; };
startLimitIntervalSec = 86400;
# 21.03+ # 21.03+
# https://github.com/NixOS/nixpkgs/pull/97512 # https://github.com/NixOS/nixpkgs/pull/97512
# startLimitBurst = 5; # startLimitIntervalSec = 14400;
# startLimitBurst = 10;
serviceConfig = { serviceConfig = {
ExecStart = '' ExecStart = "${cfg.package}/bin/caddy run --config ${cfg.config} --adapter ${cfg.adapter}";
${cfg.package}/bin/caddy -root=/var/tmp -conf=${cfg.config} ExecReload = "${cfg.package}/bin/caddy reload --config ${cfg.config} --adapter ${cfg.adapter}";
'';
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
Type = "simple"; Type = "simple";
User = "caddyProxy"; User = "caddyProxy";
Group = "caddyProxy"; Group = "caddyProxy";
Restart = "on-failure"; Restart = "on-abnormal";
# <= 20.09 StartLimitIntervalSec = 14400;
StartLimitBurst = 5; StartLimitBurst = 10;
NoNewPrivileges = true; NoNewPrivileges = true;
LimitNPROC = 64; LimitNPROC = 512;
LimitNOFILE = 1048576; LimitNOFILE = 1048576;
PrivateTmp = true; PrivateTmp = true;
PrivateDevices = true; PrivateDevices = true;
ProtectHome = true; ProtectHome = true;
ProtectSystem = "full"; ProtectSystem = "full";
ReadWriteDirectories = cfg.dataDir; ReadWriteDirectories = cfg.dataDir;
KillMode = "mixed";
KillSignal = "SIGQUIT";
TimeoutStopSec = "5s";
}; };
}; };
@ -130,7 +141,7 @@ in {
home = cfg.dataDir; home = cfg.dataDir;
createHome = true; createHome = true;
}; };
users.groups.caddyProxy = { users.groups.caddyProxy = {
members = [ "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 { mdleom.com:4430 www.mdleom.com:4430 {
tls /var/lib/caddyProxy/mdleom.com.pem /var/lib/caddyProxy/mdleom.com.key { 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. Connection to www.mdleom.com is redirected to mdleom.com with HTTP 301 status.
``` ```
redir 301 { @www host www.mdleom.com
if {label1} is www redir @www https://mdleom.com{uri} permanent
/ https://mdleom.com{uri}
}
``` ```
`{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. `{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, If you prefer to redirect apex to www,
``` ```
redir 301 { @www host mdleom.com
if {label1} is mdleom redir @www https://www.mdleom.com{uri} permanent
/ https://www.mdleom.com{uri}
}
``` ```
### Reverse proxy ### 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: In Caddyfile, the config can be expressed as:
``` plain ``` plain
proxy /img https://cdn.statically.io/img/gitlab.com/curben/blog/raw/site { handle_path /img/* {
without /img rewrite * /img/gitlab.com/curben/blog/raw/site{path}
reverse_proxy https://cdn.statically.io
} }
rewrite /screenshot { handle_path /screenshot/* {
r (.*) rewrite * /screenshot/curben.netlify.app{path}?mobile=true
to /screenshot{1}?mobile=true
reverse_proxy https://cdn.statically.io
} }
proxy /screenshot https://cdn.statically.io/screenshot/curben.netlify.app { reverse_proxy https://curben.netlify.app
without /screenshot
}
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". `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".
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}`.
### Host header ### Host header
To make sure Caddy sends the correct `Host:` header to the upstream/backend locations, I use `header_upstream` option, To make sure Caddy sends the correct `Host:` header to the upstream/backend locations, I use `header_upstream` option,
``` plain {% codeblock mark:5,13,18 %}
proxy /img https://cdn.statically.io/img/gitlab.com/curben/blog/raw/site { handle_path /img/* {
without /img rewrite * /img/gitlab.com/curben/blog/raw/site{path}
header_upstream Host cdn.statically.io
reverse_proxy https://cdn.statically.io {
header_up Host cdn.statically.io
}
} }
rewrite /screenshot { handle_path /screenshot/* {
r (.*) rewrite * /screenshot/curben.netlify.app{path}?mobile=true
to /screenshot{1}?mobile=true
reverse_proxy https://cdn.statically.io {
header_up Host cdn.statically.io
}
} }
proxy /screenshot https://cdn.statically.io/screenshot/curben.netlify.app { reverse_proxy https://curben.netlify.app {
without /screenshot header_up Host curben.netlify.app
header_upstream Host cdn.statically.io
} }
{% endcodeblock %}
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
}
}
```
### Add or remove headers ### 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) { (removeHeaders) {
header_upstream -cookie header_up -cdn-loop
header_upstream -referer header_up -cf-cache-status
# Remove Cloudflare headers header_up -cf-connecting-ip
# https://support.cloudflare.com/hc/en-us/articles/200170986-How-does-Cloudflare-handle-HTTP-Request-headers- header_up -cf-ipcountry
header_upstream -cf-ipcountry header_up -cf-ray
header_upstream -cf-connecting-ip header_up -cf-request-id
header_upstream -x-forwarded-for header_up -cf-visitor
header_upstream -x-forwarded-proto header_up -cookie
header_upstream -cf-ray header_up -referer
header_upstream -cf-visitor header_up -sec-ch-ua
header_upstream -true-client-ip header_up -sec-ch-ua-mobile
header_upstream -cdn-loop header_up -true-client-ip
header_upstream -cf-request-id header_up -via
header_upstream -cf-cache-status 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 { 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 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 / { header {
-server -access-control-allow-origin
-access-control-expose-headers
-alt-svc -alt-svc
-cdn-cache -cdn-cache
-cdn-cachedat -cdn-cachedat
@ -334,121 +346,209 @@ The upstream locations insert some information into the response headers that ar
-cdn-requestcountrycode -cdn-requestcountrycode
-cdn-requestid -cdn-requestid
-cdn-uid -cdn-uid
-cf-bgj
-cf-cache-status -cf-cache-status
-cf-polished
-cf-ray -cf-ray
-cf-request-id -cf-request-id
-content-disposition
-etag -etag
-expect-ct
-server
-set-cookie -set-cookie
-timing-allow-origin
-via
-x-bytes-saved -x-bytes-saved
-x-cache -x-cache
-x-cache-hits
-x-nf-request-id -x-nf-request-id
-x-served-by -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" 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. 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. `/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 / { header {
Cache-Control "max-age=604800, public" Cache-Control "max-age=86400, public"
} }
header /libs { header /libs/* {
Cache-Control "public, max-age=31536000, immutable" Cache-Control "public, max-age=31536000, immutable"
} }
``` ```
### Complete Caddyfile ### Complete Caddyfile
``` plain Caddyfile 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".
(removeHeaders) {
header_upstream -cookie ``` plain common.conf
header_upstream -referer ## Optional: disable admin endpoint and http->https redirect
# Remove Cloudflare headers #{
# https://support.cloudflare.com/hc/en-us/articles/200170986-How-does-Cloudflare-handle-HTTP-Request-headers- # admin off
header_upstream -cf-ipcountry # auto_https disable_redirects
header_upstream -cf-connecting-ip #}
header_upstream -x-forwarded-for
header_upstream -x-forwarded-proto (setHeaders) {
header_upstream -cf-ray -access-control-allow-origin
header_upstream -cf-visitor -access-control-expose-headers
header_upstream -true-client-ip -alt-svc
header_upstream -cdn-loop -cdn-cache
header_upstream -cf-request-id -cdn-cachedat
header_upstream -cf-cache-status -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) { (removeHeaders) {
header_downstream Strict-Transport-Security "max-age=31536000" header_up -cdn-loop
header_upstream Host cdn.statically.io 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
mdleom.com:4430 www.mdleom.com:4430 { mdleom.com:4430 www.mdleom.com:4430 {
tls /var/lib/caddyProxy/mdleom.com.pem /var/lib/caddyProxy/mdleom.com.key { 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 # www -> apex
redir 301 { @www host www.mdleom.com
if {label1} is www redir @www https://mdleom.com{uri} permanent
/ https://mdleom.com{uri}
header {
import setHeaders
Onion-Location "http://xw226dvxac7jzcpsf4xb64r4epr6o5hgn46dxlqk7gnjptakik6xnzqd.onion"
Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
defer
} }
header / { import pathProxy
-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
}
} }
``` ```
@ -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`. 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 ]; require = [ /etc/caddy/caddyProxy.nix ];
services.caddyProxy = { services.caddyProxy = {
enable = true; enable = true;

View File

@ -2,7 +2,7 @@
title: "How to make your website available over I2P Eepsite on NixOS" title: "How to make your website available over I2P Eepsite on NixOS"
excerpt: "A guide on I2P Eepsite on NixOS" excerpt: "A guide on I2P Eepsite on NixOS"
date: 2020-03-21 date: 2020-03-21
updated: 2020-09-09 updated: 2020-11-09
tags: tags:
- server - server
- linux - linux
@ -12,6 +12,8 @@ tags:
- censorship - 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/) 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: 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"; 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 { dataDir = mkOption {
default = "/var/lib/caddyI2p"; default = "/var/lib/caddyI2p";
type = types.path; type = types.path;
@ -145,40 +157,40 @@ in {
systemd.services.caddyI2p = { systemd.services.caddyI2p = {
description = "Caddy web server"; description = "Caddy web server";
after = [ "network-online.target" ]; after = [ "network-online.target" ];
wants = [ "network-online.target" ]; # systemd-networkd-wait-online.service
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
environment = mkIf (versionAtLeast config.system.stateVersion "17.09")
{ CADDYPATH = cfg.dataDir; };
startLimitIntervalSec = 86400;
# 21.03+ # 21.03+
# https://github.com/NixOS/nixpkgs/pull/97512 # https://github.com/NixOS/nixpkgs/pull/97512
# startLimitBurst = 5; # startLimitIntervalSec = 14400;
# startLimitBurst = 10;
serviceConfig = { serviceConfig = {
ExecStart = '' ExecStart = "${cfg.package}/bin/caddy run --config ${cfg.config} --adapter ${cfg.adapter}";
${cfg.package}/bin/caddy -root=/var/tmp -conf=${cfg.config} ExecReload = "${cfg.package}/bin/caddy reload --config ${cfg.config} --adapter ${cfg.adapter}";
'';
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
Type = "simple"; Type = "simple";
User = "caddyProxy"; User = "caddyI2p";
Group = "caddyProxy"; Group = "caddyI2p";
Restart = "on-failure"; Restart = "on-abnormal";
# <= 20.09 StartLimitIntervalSec = 14400;
StartLimitBurst = 5; StartLimitBurst = 10;
NoNewPrivileges = true; NoNewPrivileges = true;
LimitNPROC = 64; LimitNPROC = 512;
LimitNOFILE = 1048576; LimitNOFILE = 1048576;
PrivateTmp = true; PrivateTmp = true;
PrivateDevices = true; PrivateDevices = true;
ProtectHome = true; ProtectHome = true;
ProtectSystem = "full"; ProtectSystem = "full";
ReadWriteDirectories = cfg.dataDir; ReadWriteDirectories = cfg.dataDir;
KillMode = "mixed";
KillSignal = "SIGQUIT";
TimeoutStopSec = "5s";
}; };
}; };
users.users.caddyI2p = { users.users.caddyI2p = {
home = cfg.dataDir; home = cfg.dataDir;
createHome = true; createHome = true;
}; };
users.groups.caddyI2p = { users.groups.caddyI2p = {
members = [ "caddyI2p" ]; members = [ "caddyI2p" ];
}; };
@ -188,11 +200,13 @@ in {
### File ownership and permissions ### 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 caddyI2p:caddyI2p /etc/caddy/caddyI2p.nix
# chown 600 /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 ## 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: 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 bind ::1
tls off header {
header / {
-strict-transport-security -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 ``` plain /etc/caddy/caddyI2p.conf
(removeHeaders) { import common.conf
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
}
# I2P Eepsite # I2P Eepsite
ggucqf2jmtfxcw7us5sts3x7u2qljseocfzlhzebfpihkyvhcqfa.b32.i2p:8081 mdleom.i2p:8081 { http://ggucqf2jmtfxcw7us5sts3x7u2qljseocfzlhzebfpihkyvhcqfa.b32.i2p:8081 http://mdleom.i2p:8081 {
bind ::1 bind ::1
tls off header {
import setHeaders
header / { -strict-transport-origin
-server defer
-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 /libs { import pathProxy
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
}
} }
``` ```
### Alternate Caddyfile ### 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 # 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 bind ::1
tls off header {
header / {
-strict-transport-security -strict-transport-security
defer
} }
proxy / https://mdleom.com { reverse_proxy https://mdleom.com {
header_upstream Host mdleom.com header_up Host mdleom.com
} }
} }
``` ```
@ -318,7 +270,7 @@ ggucqf2jmtfxcw7us5sts3x7u2qljseocfzlhzebfpihkyvhcqfa.b32.i2p:8081 mdleom.i2p:808
Start the Caddy service. 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 ]; require = [ /etc/caddy/caddyProxy.nix /etc/caddy/caddyTor.nix /etc/caddy/caddyI2p.nix ];
services.caddyI2p = { services.caddyI2p = {
enable = true; enable = true;

View File

@ -2,7 +2,7 @@
title: "How to make your website available over Tor hidden service on NixOS" title: "How to make your website available over Tor hidden service on NixOS"
excerpt: "A guide on Tor hidden service on NixOS" excerpt: "A guide on Tor hidden service on NixOS"
date: 2020-03-16 date: 2020-03-16
updated: 2020-09-09 updated: 2020-11-09
tags: tags:
- server - server
- linux - linux
@ -12,6 +12,8 @@ tags:
- censorship - 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). 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: 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**: 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 ## Tor onion
services.tor = { services.tor = {
enable = true; enable = true;
@ -87,19 +89,29 @@ I set up another Caddy-powered reverse proxy which is separate from the {% post_
with lib; with lib;
let let
cfg = config.services.caddyTor; cfg = config.services.caddyProxy;
in { in {
options.services.caddyTor = { options.services.caddyProxy = {
enable = mkEnableOption "Caddy web server"; enable = mkEnableOption "Caddy web server";
config = mkOption { config = mkOption {
default = "/etc/caddy/caddyTor.conf"; default = "/etc/caddy/caddyProxy.conf";
type = types.str; type = types.str;
description = "Path to Caddyfile"; 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 { dataDir = mkOption {
default = "/var/lib/caddyTor"; default = "/var/lib/caddyProxy";
type = types.path; type = types.path;
description = '' description = ''
The data directory, for storing certificates. Before 17.09, this The data directory, for storing certificates. Before 17.09, this
@ -117,45 +129,45 @@ in {
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable {
systemd.services.caddyTor = { systemd.services.caddyProxy = {
description = "Caddy web server"; description = "Caddy web server";
after = [ "network-online.target" ]; after = [ "network-online.target" ];
wants = [ "network-online.target" ]; # systemd-networkd-wait-online.service
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
environment = mkIf (versionAtLeast config.system.stateVersion "17.09")
{ CADDYPATH = cfg.dataDir; };
startLimitIntervalSec = 86400;
# 21.03+ # 21.03+
# https://github.com/NixOS/nixpkgs/pull/97512 # https://github.com/NixOS/nixpkgs/pull/97512
# startLimitBurst = 5; # startLimitIntervalSec = 14400;
# startLimitBurst = 10;
serviceConfig = { serviceConfig = {
ExecStart = '' ExecStart = "${cfg.package}/bin/caddy run --config ${cfg.config} --adapter ${cfg.adapter}";
${cfg.package}/bin/caddy -root=/var/tmp -conf=${cfg.config} ExecReload = "${cfg.package}/bin/caddy reload --config ${cfg.config} --adapter ${cfg.adapter}";
'';
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
Type = "simple"; Type = "simple";
User = "caddyProxy"; User = "caddyProxy";
Group = "caddyProxy"; Group = "caddyProxy";
Restart = "on-failure"; Restart = "on-abnormal";
# <= 20.09 StartLimitIntervalSec = 14400;
StartLimitBurst = 5; StartLimitBurst = 10;
NoNewPrivileges = true; NoNewPrivileges = true;
LimitNPROC = 64; LimitNPROC = 512;
LimitNOFILE = 1048576; LimitNOFILE = 1048576;
PrivateTmp = true; PrivateTmp = true;
PrivateDevices = true; PrivateDevices = true;
ProtectHome = true; ProtectHome = true;
ProtectSystem = "full"; ProtectSystem = "full";
ReadWriteDirectories = cfg.dataDir; ReadWriteDirectories = cfg.dataDir;
KillMode = "mixed";
KillSignal = "SIGQUIT";
TimeoutStopSec = "5s";
}; };
}; };
users.users.caddyTor = { users.users.caddyProxy = {
home = cfg.dataDir; home = cfg.dataDir;
createHome = true; createHome = true;
}; };
users.groups.caddyTor = { users.groups.caddyProxy = {
members = [ "caddyTor" ]; 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: 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 bind ::1
tls off header {
import setHeaders
header / { -strict-transport-origin
-strict-transport-security 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 ``` plain /etc/caddy/caddyTor.conf
(removeHeaders) { import common.conf
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
}
# Tor onion # Tor onion
xw226dvxac7jzcpsf4xb64r4epr6o5hgn46dxlqk7gnjptakik6xnzqd.onion:8080 { http://xw226dvxac7jzcpsf4xb64r4epr6o5hgn46dxlqk7gnjptakik6xnzqd.onion:8080 {
bind ::1 bind ::1
tls off header {
import setHeaders
header / { -strict-transport-origin
-server defer
-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 /libs { import pathProxy
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
}
} }
``` ```
@ -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 # Do not use this approach unless you are absolutely sure
xw226dvxac7jzcpsf4xb64r4epr6o5hgn46dxlqk7gnjptakik6xnzqd.onion:8080 { http://xw226dvxac7jzcpsf4xb64r4epr6o5hgn46dxlqk7gnjptakik6xnzqd.onion:8080 {
bind ::1 bind ::1
tls off header {
header / {
-strict-transport-security -strict-transport-security
defer
} }
proxy / https://mdleom.com { reverse_proxy https://mdleom.com {
header_upstream Host mdleom.com header_up Host mdleom.com
} }
} }
``` ```