blob: 228a06ad4950aae898cdd547ac6bdfc9a3eb7901 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
|
{
me,
config,
pkgs,
lib,
...
}:
let
inherit (lib) findFirst;
# this is the https port, we use it to try to trick dpi into thinking
# we are just serving normal encrypted web traffic, nothing interesting! :)
# requests which fail vless authentication are forwarded to nginx via fallbacks.
port = 443;
# supposedly the current gold-standard protocol for circumventing dpi!
# both xray (egress-side) and sing-box (ingress-side) support various
# other protocols, if roskomnadzor learns to sniff out vless fully.
protocol = "vless";
inboundTag = "vless-in";
outboundTag = "direct-out";
# xray falls back to nginx on this port for non-vless requests.
nginxPort = 8443;
definition = import ./definition.nix;
inherit (definition) paths mask;
path = findFirst (
p: p.egress == me.name
) (throw "no egress information found for this server!") paths;
xrayConfig = {
inbounds = [
{
inherit port protocol;
tag = inboundTag;
settings = {
clients = [
{
id = path.info.uuid;
flow = "xtls-rprx-vision";
}
];
decryption = "none";
fallbacks = [
{ dest = "127.0.0.1:${toString nginxPort}"; xver = 1; }
];
};
streamSettings = {
network = "tcp";
security = "reality";
realitySettings = {
show = false;
dest = "www.${mask}:443";
serverNames = [
"www.${mask}"
mask
];
privateKey = "@PRIVATE_KEY@";
shortIds = [ path.info.short ];
};
};
}
];
# and we're out!
outbounds = [
{
protocol = "freedom";
tag = outboundTag;
}
];
routing = {
rules = [
{
type = "field";
inboundTag = [ inboundTag ];
inherit outboundTag;
}
];
};
log = {
loglevel = "debug";
};
};
config-file = pkgs.writeText "xray.json" (builtins.toJSON xrayConfig);
in
{
networking.firewall.allowedTCPPorts = [ port ];
age.secrets.egress-key = {
file = path.info.keySecret;
};
systemd.services = {
# we have to make an xray config on the fly because
# xray does not like reading secrets from specific files,
# it wants them in plain-text!
generate-xray-config = {
description = "Generate Xray configuration";
wantedBy = [ "multi-user.target" ];
before = [ "xray.service" ];
partOf = [ "xray.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
};
script = ''
mkdir -p /run/xray-configuration
cp ${config-file} /run/xray-configuration/xray.json
egress_key=$(cat ${config.age.secrets.egress-key.path})
# use sd for replacement as a fancy new tool for this
${pkgs.sd}/bin/sd "@PRIVATE_KEY@" "$egress_key" /run/xray-configuration/xray.json
'';
};
xray = {
requires = [ "generate-xray-config.service" ];
after = [ "generate-xray-config.service" ];
restartTriggers = [ config-file ];
};
};
services.xray = {
enable = true;
settingsFile = "/run/xray-configuration/xray.json";
};
# don't unwrap cloudflare addresses
foundation.www.behindCloudflare = false;
services.nginx = {
defaultListen = [
{ addr = "127.0.0.1"; port = nginxPort; ssl = true; proxyProtocol = true; }
];
# xray sends proxy protocol v1 to nginx, allowing us to
# extract the real client ip from the proxy protocol header.
commonHttpConfig = ''
set_real_ip_from 127.0.0.1;
real_ip_header proxy_protocol;
'';
};
}
|