mirror of https://gitlab.com/curben/blog
post(nixos-caddy): update to NixOS 20.09 & caddy 2.1
This commit is contained in:
parent
62ebd35ae1
commit
a1c0b6b1d0
|
@ -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, ... }:
|
||||
|
||||
{
|
||||
|
|
|
@ -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";
|
||||
};
|
||||
```
|
||||
|
|
|
@ -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";
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -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,75 +346,67 @@ 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".
|
||||
|
||||
(staticallyCfg) {
|
||||
header_downstream Strict-Transport-Security "max-age=31536000"
|
||||
header_upstream Host cdn.statically.io
|
||||
}
|
||||
``` plain common.conf
|
||||
## Optional: disable admin endpoint and http->https redirect
|
||||
#{
|
||||
# admin off
|
||||
# auto_https disable_redirects
|
||||
#}
|
||||
|
||||
## 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
|
||||
}
|
||||
|
||||
# www -> apex
|
||||
redir 301 {
|
||||
if {label1} is www
|
||||
/ https://mdleom.com{uri}
|
||||
}
|
||||
|
||||
header / {
|
||||
-server
|
||||
(setHeaders) {
|
||||
-access-control-allow-origin
|
||||
-access-control-expose-headers
|
||||
-alt-svc
|
||||
-cdn-cache
|
||||
-cdn-cachedat
|
||||
|
@ -411,44 +415,140 @@ mdleom.com:4430 www.mdleom.com:4430 {
|
|||
-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
|
||||
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"
|
||||
}
|
||||
|
||||
(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 /libs {
|
||||
Cache-Control "public, max-age=31536000, immutable"
|
||||
header /css/* {
|
||||
import oneWeekCache
|
||||
defer
|
||||
}
|
||||
|
||||
proxy /img https://cdn.statically.io/img/gitlab.com/curben/blog/raw/site {
|
||||
without /img
|
||||
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
|
||||
import staticallyCfg
|
||||
header_up Host cdn.statically.io
|
||||
}
|
||||
}
|
||||
|
||||
rewrite /screenshot {
|
||||
r (.*)
|
||||
to /screenshot{1}?mobile=true
|
||||
header /img/* {
|
||||
import oneWeekCache
|
||||
defer
|
||||
}
|
||||
|
||||
proxy /screenshot https://cdn.statically.io/screenshot/curben.netlify.app {
|
||||
without /screenshot
|
||||
handle_path /screenshot/* {
|
||||
rewrite * /screenshot/curben.netlify.app{path}?mobile=true
|
||||
|
||||
reverse_proxy https://cdn.statically.io {
|
||||
import removeHeaders
|
||||
import staticallyCfg
|
||||
header_up Host cdn.statically.io
|
||||
}
|
||||
}
|
||||
|
||||
proxy / https://curben.netlify.app {
|
||||
import removeHeaders
|
||||
header_upstream Host curben.netlify.app
|
||||
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 {
|
||||
protocols tls1.3
|
||||
client_auth {
|
||||
mode require_and_verify
|
||||
trusted_ca_cert_file /var/lib/caddyProxy/origin-pull-ca.pem
|
||||
}
|
||||
}
|
||||
|
||||
# www -> apex
|
||||
@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
|
||||
}
|
||||
|
||||
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;
|
||||
|
|
|
@ -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,32 +157,32 @@ 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";
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
Loading…
Reference in New Issue