Skip to main content

max / makenotwork

Cloudflare Origin CA + Authenticated Origin Pulls, mnw-admin rebuild-keys fix Switch TLS from Caddy auto-provisioned Let's Encrypt to Cloudflare Origin CA wildcard cert with Authenticated Origin Pulls (mTLS). Origin IP hidden behind Cloudflare proxy for all HTTP/HTTPS subdomains. .com redirects moved to Cloudflare edge rules. Fix rebuild-keys to chown git:git after writing authorized_keys. Bump to v0.2.7. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author: Max J. <87768334+MaxJMath@users.noreply.github.com> · 2026-03-15 17:26 UTC
Commit: 0c29e3fad432f8d73ae5c6b145dcdc2ad5b808fe
Parent: ae10c93
5 files changed, +139 insertions, -60 deletions
@@ -3453,7 +3453,7 @@ dependencies = [
3453 3453
3454 3454 [[package]]
3455 3455 name = "makenotwork"
3456 - version = "0.2.5"
3456 + version = "0.2.6"
3457 3457 dependencies = [
3458 3458 "ammonia",
3459 3459 "anyhow",
@@ -1,6 +1,6 @@
1 1 [package]
2 2 name = "makenotwork"
3 - version = "0.2.6"
3 + version = "0.2.7"
4 4 edition = "2024"
5 5 license-file = "../../LICENSE"
6 6
@@ -1,69 +1,105 @@
1 1 # Makenotwork Caddy Configuration
2 2 # Place in /etc/caddy/Caddyfile on the server
3 + #
4 + # TLS: Cloudflare Origin CA cert (wildcard *.makenot.work + makenot.work)
5 + # All HTTPS traffic routed through Cloudflare proxy (origin IP hidden).
6 + # Authenticated Origin Pulls: only Cloudflare can reach the origin.
7 + # git.makenot.work is SSH-only (no Caddy block needed, port 22 via sshd).
8 +
9 + # Shared TLS config: Origin CA cert + Authenticated Origin Pulls (mTLS)
10 + (cloudflare_tls) {
11 + tls /etc/caddy/cloudflare-origin.pem /etc/caddy/cloudflare-origin-key.pem {
12 + client_auth {
13 + mode require_and_verify
14 + trusted_ca_cert_file /etc/caddy/cloudflare-authenticated-origin-pull-ca.pem
15 + }
16 + }
17 + }
3 18
4 19 makenot.work {
5 - # Serve documentation as static files
6 - handle /docs/* {
7 - root * /opt/makenotwork/docs
8 - uri strip_prefix /docs
9 - file_server
10 - }
20 + import cloudflare_tls
21 +
22 + # Serve documentation as static files
23 + handle /docs/* {
24 + root * /opt/makenotwork/docs
25 + uri strip_prefix /docs
26 + file_server
27 + }
28 +
29 + # Reverse proxy to application
30 + handle {
31 + reverse_proxy localhost:3000
32 + }
33 +
34 + # Security headers
35 + header {
36 + X-Frame-Options "SAMEORIGIN"
37 + X-Content-Type-Options "nosniff"
38 + X-XSS-Protection "1; mode=block"
39 + Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
40 + Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(self)"
41 + Referrer-Policy "strict-origin-when-cross-origin"
42 + Content-Security-Policy "default-src 'none'; script-src 'self' 'unsafe-inline' https://unpkg.com https://js.stripe.com; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self' data: https://fsn1.your-objectstorage.com; connect-src 'self' https://api.stripe.com https://fsn1.your-objectstorage.com; media-src 'self' https://fsn1.your-objectstorage.com; frame-src https://js.stripe.com; base-uri 'self'; form-action 'self'"
43 + }
44 +
45 + # Static error pages when app is down
46 + handle_errors {
47 + @404 expression {err.status_code} == 404
48 + handle @404 {
49 + root * /opt/makenotwork/error-pages
50 + rewrite * /404.html
51 + file_server
52 + }
53 + @500 expression {err.status_code} == 500
54 + handle @500 {
55 + root * /opt/makenotwork/error-pages
56 + rewrite * /500.html
57 + file_server
58 + }
59 + handle {
60 + root * /opt/makenotwork/error-pages
61 + rewrite * /502.html
62 + file_server
63 + }
64 + }
65 +
66 + encode gzip zstd
67 +
68 + log {
69 + output file /var/log/caddy/makenotwork.log
70 + format json
71 + }
72 + }
11 73
12 - # Reverse proxy to application
13 - handle {
14 - reverse_proxy localhost:3000
15 - }
74 + # Multithreaded forum
75 + forums.makenot.work {
76 + import cloudflare_tls
16 77
17 - # Security headers
18 - header {
19 - # Prevent clickjacking
20 - X-Frame-Options "SAMEORIGIN"
21 - # Prevent MIME type sniffing
22 - X-Content-Type-Options "nosniff"
23 - # Enable XSS filter
24 - X-XSS-Protection "1; mode=block"
25 - # HSTS — browsers never attempt HTTP
26 - Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
27 - # Lock down unused browser APIs
28 - Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(self)"
29 - # Referrer policy
30 - Referrer-Policy "strict-origin-when-cross-origin"
31 - # Content Security Policy
32 - Content-Security-Policy "default-src 'none'; script-src 'self' 'unsafe-inline' https://unpkg.com https://js.stripe.com; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self' data: https://fsn1.your-objectstorage.com; connect-src 'self' https://api.stripe.com https://fsn1.your-objectstorage.com; media-src 'self' https://fsn1.your-objectstorage.com; frame-src https://js.stripe.com; base-uri 'self'; form-action 'self'"
33 - }
78 + reverse_proxy localhost:3400
34 79
35 - # Static error pages when app is down
36 - handle_errors {
37 - @404 expression {err.status_code} == 404
38 - handle @404 {
39 - root * /opt/makenotwork/error-pages
40 - rewrite * /404.html
41 - file_server
42 - }
43 - @500 expression {err.status_code} == 500
44 - handle @500 {
45 - root * /opt/makenotwork/error-pages
46 - rewrite * /500.html
47 - file_server
48 - }
49 - handle {
50 - root * /opt/makenotwork/error-pages
51 - rewrite * /502.html
52 - file_server
53 - }
54 - }
80 + header {
81 + X-Frame-Options "SAMEORIGIN"
82 + X-Content-Type-Options "nosniff"
83 + X-XSS-Protection "1; mode=block"
84 + Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"
85 + Permissions-Policy "camera=(), microphone=(), geolocation=()"
86 + Referrer-Policy "strict-origin-when-cross-origin"
87 + Content-Security-Policy "default-src 'none'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self' data:; connect-src 'self'; base-uri 'self'; form-action 'self' https://makenot.work"
88 + }
55 89
56 - # Compression
57 - encode gzip zstd
90 + encode gzip zstd
58 91
59 - # Logging
60 - log {
61 - output file /var/log/caddy/makenotwork.log
62 - format json
63 - }
92 + log {
93 + output file /var/log/caddy/forums.log
94 + format json
95 + }
64 96 }
65 97
66 - # Redirect www and .com variants to canonical domain
67 - www.makenot.work, makenotwork.com, www.makenotwork.com {
68 - redir https://makenot.work{uri} permanent
98 + # Redirect www to canonical domain
99 + # Note: makenotwork.com and www.makenotwork.com redirects are handled by
100 + # Cloudflare Redirect Rules (edge-level, no origin hit needed).
101 + # Those domains are not covered by the *.makenot.work Origin CA cert.
102 + www.makenot.work {
103 + import cloudflare_tls
104 + redir https://makenot.work{uri} permanent
69 105 }
@@ -0,0 +1,35 @@
1 + -----BEGIN CERTIFICATE-----
2 + MIIGCjCCA/KgAwIBAgIIV5G6lVbCLmEwDQYJKoZIhvcNAQENBQAwgZAxCzAJBgNV
3 + BAYTAlVTMRkwFwYDVQQKExBDbG91ZEZsYXJlLCBJbmMuMRQwEgYDVQQLEwtPcmln
4 + aW4gUHVsbDEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzETMBEGA1UECBMKQ2FsaWZv
5 + cm5pYTEjMCEGA1UEAxMab3JpZ2luLXB1bGwuY2xvdWRmbGFyZS5uZXQwHhcNMTkx
6 + MDEwMTg0NTAwWhcNMjkxMTAxMTcwMDAwWjCBkDELMAkGA1UEBhMCVVMxGTAXBgNV
7 + BAoTEENsb3VkRmxhcmUsIEluYy4xFDASBgNVBAsTC09yaWdpbiBQdWxsMRYwFAYD
8 + VQQHEw1TYW4gRnJhbmNpc2NvMRMwEQYDVQQIEwpDYWxpZm9ybmlhMSMwIQYDVQQD
9 + ExpvcmlnaW4tcHVsbC5jbG91ZGZsYXJlLm5ldDCCAiIwDQYJKoZIhvcNAQEBBQAD
10 + ggIPADCCAgoCggIBAN2y2zojYfl0bKfhp0AJBFeV+jQqbCw3sHmvEPwLmqDLqynI
11 + 42tZXR5y914ZB9ZrwbL/K5O46exd/LujJnV2b3dzcx5rtiQzso0xzljqbnbQT20e
12 + ihx/WrF4OkZKydZzsdaJsWAPuplDH5P7J82q3re88jQdgE5hqjqFZ3clCG7lxoBw
13 + hLaazm3NJJlUfzdk97ouRvnFGAuXd5cQVx8jYOOeU60sWqmMe4QHdOvpqB91bJoY
14 + QSKVFjUgHeTpN8tNpKJfb9LIn3pun3bC9NKNHtRKMNX3Kl/sAPq7q/AlndvA2Kw3
15 + Dkum2mHQUGdzVHqcOgea9BGjLK2h7SuX93zTWL02u799dr6Xkrad/WShHchfjjRn
16 + aL35niJUDr02YJtPgxWObsrfOU63B8juLUphW/4BOjjJyAG5l9j1//aUGEi/sEe5
17 + lqVv0P78QrxoxR+MMXiJwQab5FB8TG/ac6mRHgF9CmkX90uaRh+OC07XjTdfSKGR
18 + PpM9hB2ZhLol/nf8qmoLdoD5HvODZuKu2+muKeVHXgw2/A6wM7OwrinxZiyBk5Hh
19 + CvaADH7PZpU6z/zv5NU5HSvXiKtCzFuDu4/Zfi34RfHXeCUfHAb4KfNRXJwMsxUa
20 + +4ZpSAX2G6RnGU5meuXpU5/V+DQJp/e69XyyY6RXDoMywaEFlIlXBqjRRA2pAgMB
21 + AAGjZjBkMA4GA1UdDwEB/wQEAwIBBjASBgNVHRMBAf8ECDAGAQH/AgECMB0GA1Ud
22 + DgQWBBRDWUsraYuA4REzalfNVzjann3F6zAfBgNVHSMEGDAWgBRDWUsraYuA4REz
23 + alfNVzjann3F6zANBgkqhkiG9w0BAQ0FAAOCAgEAkQ+T9nqcSlAuW/90DeYmQOW1
24 + QhqOor5psBEGvxbNGV2hdLJY8h6QUq48BCevcMChg/L1CkznBNI40i3/6heDn3IS
25 + zVEwXKf34pPFCACWVMZxbQjkNRTiH8iRur9EsaNQ5oXCPJkhwg2+IFyoPAAYURoX
26 + VcI9SCDUa45clmYHJ/XYwV1icGVI8/9b2JUqklnOTa5tugwIUi5sTfipNcJXHhgz
27 + 6BKYDl0/UP0lLKbsUETXeTGDiDpxZYIgbcFrRDDkHC6BSvdWVEiH5b9mH2BON60z
28 + 0O0j8EEKTwi9jnafVtZQXP/D8yoVowdFDjXcKkOPF/1gIh9qrFR6GdoPVgB3SkLc
29 + 5ulBqZaCHm563jsvWb/kXJnlFxW+1bsO9BDD6DweBcGdNurgmH625wBXksSdD7y/
30 + fakk8DagjbjKShYlPEFOAqEcliwjF45eabL0t27MJV61O/jHzHL3dknXeE4BDa2j
31 + bA+JbyJeUMtU7KMsxvx82RmhqBEJJDBCJ3scVptvhDMRrtqDBW5JShxoAOcpFQGm
32 + iYWicn46nPDjgTU0bX1ZPpTpryXbvciVL5RkVBuyX2ntcOLDPlZWgxZCBp96x07F
33 + AnOzKgZk4RzZPNAxCXERVxajn/FLcOhglVAKo5H0ac+AitlQ0ip55D2/mf8o72tM
34 + fVQ6VpyjEXdiIXWUq/o=
35 + -----END CERTIFICATE-----
@@ -617,11 +617,19 @@ async fn cmd_rebuild_keys(pool: &PgPool) -> anyhow::Result<()> {
617 617 std::fs::write(&tmp_path, &content)?;
618 618 std::fs::rename(&tmp_path, AUTHORIZED_KEYS_PATH)?;
619 619
620 - // Set permissions to 600
620 + // Set ownership to git:git and permissions to 600.
621 + // mnw-admin runs as root, but sshd requires authorized_keys owned by the target user.
621 622 #[cfg(unix)]
622 623 {
623 624 use std::os::unix::fs::PermissionsExt;
624 625 std::fs::set_permissions(AUTHORIZED_KEYS_PATH, std::fs::Permissions::from_mode(0o600))?;
626 +
627 + let status = std::process::Command::new("chown")
628 + .args(["git:git", AUTHORIZED_KEYS_PATH])
629 + .status()?;
630 + if !status.success() {
631 + anyhow::bail!("chown git:git failed on {}", AUTHORIZED_KEYS_PATH);
632 + }
625 633 }
626 634
627 635 println!("Rebuilt authorized_keys with {} key(s).", keys.len());