summaryrefslogtreecommitdiffstats
path: root/machines/jormungand/nginx.nix
blob: bdec053fd81e3b4f2449ebd97c9a0f967c50cbba (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
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
{ config, pkgs, ... }:

let
  cryptpadConfig = ''
    # CryptPad serves static assets over these two domains.
    # `main_domain` is what users will enter in their address bar.
    # Privileged computation such as key management is handled in this scope
    # UI content is loaded via the `sandbox_domain`.
    # "Content Security Policy" headers prevent content loaded via the sandbox
    # from accessing privileged information.
    # These variables must be different to take advantage of CryptPad's sandboxing techniques.
    # In the event of an XSS vulnerability in CryptPad's front-end code
    # this will limit the amount of information accessible to attackers.
    set $main_domain "cryptpad.codewreck.org";
    set $sandbox_domain "cryptpad-sandbox.codewreck.org";

    # CryptPad's dynamic content (websocket traffic and encrypted blobs)
    # can be served over separate domains. Using dedicated domains (or subdomains)
    # for these purposes allows you to move them to a separate machine at a later date
    # if you find that a single machine cannot handle all of your users.
    # If you don't use dedicated domains, this can be the same as $main_domain
    # If you do, they'll be added as exceptions to any rules which block connections to remote domains.
    set $api_domain "cryptpad.codewreck.org";
    set $files_domain "cryptpad.codewreck.org";

    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options nosniff;
    add_header Access-Control-Allow-Origin "*";
    # add_header X-Frame-Options "SAMEORIGIN";

    # Insert the path to your CryptPad repository root here
    root ${pkgs.cryptpad}/lib/node_modules/cryptpad;
    index index.html;
    error_page 404 /customize.dist/404.html;

    # any static assets loaded with "ver=" in their URL will be cached for a year
    if ($args ~ ver=) {
        set $cacheControl max-age=31536000;
    }
    # Will not set any header if it is emptystring
    add_header Cache-Control $cacheControl;

    # CSS can be dynamically set inline, loaded from the same domain, or from $main_domain
    set $styleSrc   "'unsafe-inline' 'self' ''${main_domain}";

    # connect-src restricts URLs which can be loaded using script interfaces
    set $connectSrc "'self' https://''${main_domain} ''${main_domain} https://''${api_domain} blob: wss://''${api_domain} ''${api_domain} ''${files_domain}";

    # fonts can be loaded from data-URLs or the main domain
    set $fontSrc    "'self' data: ''${main_domain}";

    # images can be loaded from anywhere, though we'd like to deprecate this as it allows the use of images for tracking
    set $imgSrc     "'self' data: * blob: ''${main_domain}";

    # frame-src specifies valid sources for nested browsing contexts.
    # this prevents loading any iframes from anywhere other than the sandbox domain
    set $frameSrc   "'self' ''${sandbox_domain} blob:";

    # specifies valid sources for loading media using video or audio
    set $mediaSrc   "'self' data: * blob: ''${main_domain}";

    # defines valid sources for webworkers and nested browser contexts
    # deprecated in favour of worker-src and frame-src
    set $childSrc   "https://''${main_domain}";

    # specifies valid sources for Worker, SharedWorker, or ServiceWorker scripts.
    # supercedes child-src but is unfortunately not yet universally supported.
    set $workerSrc  "https://''${main_domain}";

    # script-src specifies valid sources for javascript, including inline handlers
    set $scriptSrc  "'self' resource: ''${main_domain}";

    set $unsafe 0;
    # the following assets are loaded via the sandbox domain
    # they unfortunately still require exceptions to the sandboxing to work correctly.
    if ($uri = "/pad/inner.html") { set $unsafe 1; }
    if ($uri = "/sheet/inner.html") { set $unsafe 1; }
    if ($uri ~ ^\/common\/onlyoffice\/.*\/index\.html.*$) { set $unsafe 1; }

    # everything except the sandbox domain is a privileged scope, as they might be used to handle keys
    if ($host != $sandbox_domain) { set $unsafe 0; }

    # privileged contexts allow a few more rights than unprivileged contexts, though limits are still applied
    if ($unsafe) {
        set $scriptSrc "'self' 'unsafe-eval' 'unsafe-inline' resource: ''${main_domain}";
    }

    # Finally, set all the rules you composed above.
    add_header Content-Security-Policy "default-src 'none'; child-src $childSrc; worker-src $workerSrc; media-src $mediaSrc; style-src $styleSrc; script-src $scriptSrc; connect-src $connectSrc; font-src $fontSrc; img-src $imgSrc; frame-src $frameSrc;";

    # The nodejs process can handle all traffic whether accessed over websocket or as static assets
    # We prefer to serve static content from nginx directly and to leave the API server to handle
    # the dynamic content that only it can manage. This is primarily an optimization
    location ^~ /cryptpad_websocket {
        proxy_pass http://[::1]:3000;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        # WebSocket support (nginx 1.4)
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection upgrade;
    }

    location ^~ /customize.dist/ {
        # This is needed in order to prevent infinite recursion between /customize/ and the root
    }
    # try to load customizeable content via /customize/ and fall back to the default content
    # located at /customize.dist/
    # This is what allows you to override behaviour.
    location ^~ /customize/ {
        rewrite ^/customize/(.*)$ $1 break;
        try_files /customize/$uri /customize.dist/$uri;
    }

    # /api/config is loaded once per page load and is used to retrieve
    # the caching variable which is applied to every other resource
    # which is loaded during that session.
    location = /api/config {
        proxy_pass http://[::1]:3000;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }

    # encrypted blobs are immutable and are thus cached for a year
    location ^~ /blob/ {
        root /www/cryptpad;
        add_header X-XSS-Protection "1; mode=block";
        add_header X-Content-Type-Options nosniff;
        add_header Content-Security-Policy "default-src 'none'; child-src $childSrc; worker-src $workerSrc; media-src $mediaSrc; style-src $styleSrc; script-src $scriptSrc; connect-src $connectSrc; font-src $fontSrc; img-src $imgSrc; frame-src $frameSrc;";
        if ($request_method = 'OPTIONS') {
            add_header Cache-Control $cacheControl;
            add_header X-XSS-Protection "1; mode=block";
            add_header X-Content-Type-Options nosniff;
            add_header Content-Security-Policy "default-src 'none'; child-src $childSrc; worker-src $workerSrc; media-src $mediaSrc; style-src $styleSrc; script-src $scriptSrc; connect-src $connectSrc; font-src $fontSrc; img-src $imgSrc; frame-src $frameSrc;";
            add_header 'Access-Control-Allow-Origin' '*';
            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
            add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
            add_header 'Access-Control-Max-Age' 1728000;
            add_header 'Content-Type' 'application/octet-stream; charset=utf-8';
            add_header 'Content-Length' 0;
            return 204;
        }
        add_header Cache-Control max-age=31536000;
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
        add_header 'Access-Control-Expose-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Content-Range,Range';
        try_files $uri =404;
    }

    # the "block-store" serves encrypted payloads containing users' drive keys
    # these payloads are unlocked via login credentials. They are mutable
    # and are thus never cached. They're small enough that it doesn't matter, in any case.
    location ^~ /block/ {
        root /www/cryptpad;
        add_header X-XSS-Protection "1; mode=block";
        add_header X-Content-Type-Options nosniff;
        add_header Access-Control-Allow-Origin "*";
        add_header Content-Security-Policy "default-src 'none'; child-src $childSrc; worker-src $workerSrc; media-src $mediaSrc; style-src $styleSrc; script-src $scriptSrc; connect-src $connectSrc; font-src $fontSrc; img-src $imgSrc; frame-src $frameSrc;";
        add_header Cache-Control max-age=0;
        try_files $uri =404;
    }

    # This block provides an alternative means of loading content
    # otherwise only served via websocket. This is solely for debugging purposes,
    # and is thus not allowed by default.
    #location ^~ /datastore/ { # nixos: possibly also blobstage, data, pins, tasks
        #root /www/cryptpad;
        #add_header Cache-Control max-age=0;
        #try_files $uri =404;
    #}

    # ugly hack: disable registration
    location /register {
        deny all;
    }


    # The nodejs server has some built-in forwarding rules to prevent
    # URLs like /pad from resulting in a 404. This simply adds a trailing slash
    # to a variety of applications.
    location ~ ^/(register|login|settings|user|pad|drive|poll|slide|code|whiteboard|file|media|profile|contacts|todo|filepicker|debug|kanban|sheet|support|admin|notifications|teams)$ {
        rewrite ^(.*)$ $1/ redirect;
    }

    # Finally, serve anything the above exceptions don't govern.
    try_files /www/$uri /www/$uri/index.html /customize/$uri;
  '';
in {

  imports = [ ../../profiles/nginx.nix ];

  systemd.services.nginx = {
    serviceConfig.BindReadOnlyPaths = [
      "/home/asmadeus/www:/www/local"
      "/var/lib/private/cryptpad:/www/cryptpad"
    ];
  };

  services.nginx.virtualHosts =  {
    "jormungand.codewreck.org" = {
      forceSSL = true;
      enableACME = true;
      locations."/mpd/" = {
        proxyPass = "http://127.0.0.1:8080";
        # /var/spool/nginx/mpd.htpasswd has been populated manually
        # until proper secrets get managed...
        extraConfig = ''
          auth_basic "mpd";
          auth_basic_user_file /var/spool/nginx/mpd.htpasswd;
        '';
      };
      locations."/local/" = {
        root = "/www";
      };
      locations."/" = {
        extraConfig = ''
          rewrite ^ https://codewreck.org$request_uri? redirect;
        '';
      };
    };

    "matrix.codewreck.org" = {
      forceSSL = true;
      enableACME = true;
      locations."/".extraConfig = ''
        return 404;
      '';
      locations."/_matrix" = {
        proxyPass = "http://[::1]:8008";
      };
    };

    "riot.codewreck.org" = {
      forceSSL = true;
      enableACME = true;
      root = pkgs.element-web.override {
        conf = {
          default_server_config."m.homeserver" = {
            "base_url" = "https://matrix.codewreck.org";
            "server_name" = "codewreck.org";
          };
        };
      };
    };

    "cryptpad.codewreck.org" = {
      forceSSL = true;
      enableACME = true;
      extraConfig = cryptpadConfig;
    };
    "cryptpad-sandbox.codewreck.org" = {
      forceSSL = true;
      enableACME = true;
      extraConfig = cryptpadConfig;
    };
  };
}