summary refs log tree commit diff
path: root/modules/tunnel/egress.nix
blob: 4334f80f43d081b1c898e39cc22ef33f51eddf7d (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
{
  me,
  config,
  pkgs,
  lib,
  ...
}:

let
  inherit (lib) findFirst mkForce;

  # 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! :)
  # this does mean that our egress servers are unable to support normal www
  # services which we put on machines by default, which is okay.
  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";

  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";
        };

        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";
  };

  # nginx can not run on an egress node because xray has to run on port 443.
  services.nginx.enable = mkForce false;
  systemd.services.nginx.enable = mkForce false;
}