很早之前写过一篇《使用 Nginx 反代 Mastodon》,但那篇主要指对没有站点管理权的情况下如何添加反代域名,而且年代久远一些内容也需要更新。
应嘎嘎的要求,今天特别写一篇博文讲一讲 Mastodon 域名设置相关的内容。
本文篇幅可能会比较长,前半部分将大致讲解 Mastodon 的架构以及域名相关设置,后半部分将讲解如何更换 WEB_DOMAIN、如何添加反代域名。
Mastodon 大体架构
Mastodon 是一个前后端分离的应用,前端是使用 React 写成的单页应用,负责交互与展示;后端由 Ruby 写成负表提供 API 。
当用户访问时,HTML、XHR 请求反代至 Puma 服务, websocket 请求反代至 Node 服务,其余静态资源由 nginx 响应。
Mastodon 服务发现流程
众所周知,Mastodon 是一个基于 ActivityPub 协议联邦式的去中心化平台。
那么问题来了,Mastodon 是如何发现远程实例的?或者说当你在编辑框中写下 @[email protected]
后,Mastodon 是如何将其翻译为 ActivityPub 协议所需的 HTTPS URI 的?
Mastodon 帐户的两个身份标识:
webfinger
acct
URI:跨实例的可验证的全局用户身份标识。actor URI:用于 federation 过程的其他所有方面。
Mastodon 域名配置
在 Mastodon 配置文件中,与用户使用相关的域名选项有 LOCAL_DOMAIN
、WEB_DOMAIN
、ALTERNATE_DOMAINS
、STREAMING_API_BASE_URL
、CDN_HOST
、S3_ALIAS_HOST
。
LOCAL_DOMAIN
服务器在 Fediverse 网络中唯一标识,用于生成帐户 acct
URI,确认后无法更改,修改该项配置将导致帐户 acct
URI 改变,远程服务器会将现有帐户视为不同于之前的全新帐户。
例如: @[email protected]
将实例的 LOCAL_DOMAIN
由 mastodon.qpomelo.app
改为 qpomelo.cc
,其acct也从 acct:[email protected]
变更为 acct:[email protected]
。
虽然实际上是同一个帐户,但由于acct不同,被远程实例视为两个不同的帐户。
WEB_DOMAIN
可选配置项,默认与 LOCAL_DOMAIN
相同,用于生成网页内容,诸如:actor
、 inbox
等。通过设置 WEB_DOMAIN
,可以将 Mastodon 服务运行于另一域名。
例如:假设 bgme.me
已经存在其它服务,将 LOCAL_DOMAIN
设为 bgme.me
,将 WEB_DOMAIN
设为 mastodon.bgme.me
,便可以保证帐户 acct
URI 以 bgme.me
结尾的情况下将 Mastodon 相关服务运行在 mastodon.bgme.me
域名下。
GET /.well-known/host-meta HTTP/1.1 Accept: */* Accept-Encoding: gzip, deflate, br Connection: keep-alive Host: naraku.cc User-Agent: HTTPie/3.2.1 HTTP/1.1 301 Moved Permanently CF-Cache-Status: DYNAMIC CF-RAY: 77ef38a2ef452ac1-LAX Connection: keep-alive Content-Type: text/html Date: Sun, 25 Dec 2022 05:36:49 GMT NEL: {"success_fraction":0,"report_to":"cf-nel","max_age":604800} Report-To: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=iZw4%2BWLYieZU2hKsOvMH4bRt1nJSgQkvZgaiWgY7GsvaBWsDvOB8W0sLcPo7LHzmungp42AkWLkEfcU%2BTG2hrK%2F6jSOTLMlOkNkNNJVna7CjhUaukV%2FOvBrf6Mu5izCV0HKz9BfZq%2FA%3D"}],"group":"cf-nel","max_age":604800} Server: cloudflare Strict-Transport-Security: max-age=15552000; includeSubDomains; preload Transfer-Encoding: chunked X-Content-Type-Options: nosniff access-control-allow-origin: * alt-svc: h3=":443"; ma=86400, h3-29=":443"; ma=86400 location: https://mtd.naraku.cc/.well-known/host-meta <html> <head><title>301 Moved Permanently</title></head> <body> <center><h1>301 Moved Permanently</h1></center> <hr><center>nginx</center> </body> </html>
GET /.well-known/host-meta HTTP/1.1 Accept: */* Accept-Encoding: gzip, deflate, br Connection: keep-alive Host: mtd.naraku.cc User-Agent: HTTPie/3.2.1 HTTP/1.1 200 OK CF-Cache-Status: DYNAMIC CF-RAY: 77ef36c5efab527b-LAX Cache-Control: max-age=259200, public Connection: keep-alive Content-Type: application/xrd+xml; charset=utf-8 Date: Sun, 25 Dec 2022 05:35:33 GMT NEL: {"success_fraction":0,"report_to":"cf-nel","max_age":604800} Report-To: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=oXXheIvbL6bzP4E2Kp%2F6wgvMn8cvr2bXJgXNl8bO%2F86r4Gdy4z7fp3y7SMwVAAORKiy5Qh5rjVx6TKaLhpnHT7D2Xwh58g1r1W4HU6n%2BnJgnZZWaeqqnyMhZKEpHXTkJMkTwKT4FUtkefGU%2F"}],"group":"cf-nel","max_age":604800} Server: cloudflare Transfer-Encoding: chunked alt-svc: h3=":443"; ma=86400, h3-29=":443"; ma=86400 content-security-policy: base-uri 'none'; default-src 'none'; frame-ancestors 'none'; font-src 'self' https://mtd.naraku.cc; img-src 'self' https: data: blob: https://mtd.naraku.cc; style-src 'self' https://mtd.naraku.cc 'nonce-PXsk8y8bu+tcBwEB5toFkw=='; media-src 'self' https: data: https://mtd.naraku.cc; frame-src 'self' https:; manifest-src 'self' https://mtd.naraku.cc; connect-src 'self' data: blob: https://mtd.naraku.cc https://mtd.naraku.cc wss://mtd.naraku.cc; script-src 'self' https://mtd.naraku.cc 'wasm-unsafe-eval'; child-src 'self' blob: https://mtd.naraku.cc; worker-src 'self' blob: https://mtd.naraku.cc etag: W/"5205d754e2b6177b4be99ecc2e1413a7" permissions-policy: interest-cohort=() strict-transport-security: max-age=15552000; includeSubDomains; preload vary: Accept, Origin x-content-type-options: nosniff x-frame-options: DENY x-request-id: cd29d011-08e4-4117-b31b-f1f5c0f04c4b x-runtime: 0.004306 x-xss-protection: 0 <?xml version="1.0" encoding="UTF-8"?> <XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"> <Link rel="lrdd" template="https://mtd.naraku.cc/.well-known/webfinger?resource={uri}"/> </XRD>
http "https://mtd.naraku.cc/.well-known/webfinger?resource=acct:[email protected]" -p HBhb
GET /.well-known/webfinger?resource=acct:[email protected] HTTP/1.1 Accept: */* Accept-Encoding: gzip, deflate, br Connection: keep-alive Host: mtd.naraku.cc User-Agent: HTTPie/3.2.1 HTTP/1.1 200 OK CF-Cache-Status: DYNAMIC CF-RAY: 77ef33764ff15214-LAX Cache-Control: max-age=259200, public Connection: keep-alive Content-Type: application/jrd+json; charset=utf-8 Date: Sun, 25 Dec 2022 05:33:17 GMT NEL: {"success_fraction":0,"report_to":"cf-nel","max_age":604800} Report-To: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=vQy9au8mhZlm8cgjGpTkPxU%2BOODjpwPh1jRwdMGLJZWblTWLidQyqgfQclB7%2BnFwA08aJYS2sQhoSoww7scjtJ6FQ2BVzUQan9TWZ4TIg%2BVHWx9oKDudox6VWThj9cDg9uyqp%2FU6C%2BsxqBmx"}],"group":"cf-nel","max_age":604800} Server: cloudflare Transfer-Encoding: chunked alt-svc: h3=":443"; ma=86400, h3-29=":443"; ma=86400 content-security-policy: base-uri 'none'; default-src 'none'; frame-ancestors 'none'; font-src 'self' https://mtd.naraku.cc; img-src 'self' https: data: blob: https://mtd.naraku.cc; style-src 'self' https://mtd.naraku.cc 'nonce-HtWXbWc6yrKVW32Aqzv4Cw=='; media-src 'self' https: data: https://mtd.naraku.cc; frame-src 'self' https:; manifest-src 'self' https://mtd.naraku.cc; connect-src 'self' data: blob: https://mtd.naraku.cc https://mtd.naraku.cc wss://mtd.naraku.cc; script-src 'self' https://mtd.naraku.cc 'wasm-unsafe-eval'; child-src 'self' blob: https://mtd.naraku.cc; worker-src 'self' blob: https://mtd.naraku.cc etag: W/"b465d414f933c0013f1e193073fabba4" permissions-policy: interest-cohort=() strict-transport-security: max-age=15552000; includeSubDomains; preload vary: Accept, Origin x-content-type-options: nosniff x-frame-options: DENY x-request-id: f6aa211d-bab6-47a1-87a7-fdd97b511e8b x-runtime: 0.008764 x-xss-protection: 0 { "aliases": [ "https://mtd.naraku.cc/@naraku", "https://mtd.naraku.cc/users/naraku" ], "links": [ { "href": "https://mtd.naraku.cc/@naraku", "rel": "http://webfinger.net/rel/profile-page", "type": "text/html" }, { "href": "https://mtd.naraku.cc/users/naraku", "rel": "self", "type": "application/activity+json" }, { "rel": "http://ostatus.org/schema/1.0/subscribe", "template": "https://mtd.naraku.cc/authorize_interaction?uri={uri}" } ], "subject": "acct:[email protected]" }
GET /users/naraku HTTP/1.1 Accept: application/activity+json Accept-Encoding: gzip, deflate, br Connection: keep-alive Host: mtd.naraku.cc User-Agent: HTTPie/3.2.1 HTTP/1.1 200 OK CF-Cache-Status: DYNAMIC CF-RAY: 77ef47fb4c7f2b50-LAX Cache-Control: max-age=180, public Connection: keep-alive Content-Type: application/activity+json; charset=utf-8 Date: Sun, 25 Dec 2022 05:47:18 GMT NEL: {"success_fraction":0,"report_to":"cf-nel","max_age":604800} Report-To: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=p9ppDYwQMI6z5Sc%2BY1FlfE2BxwxXfd%2F7yQSwwrQdyRHcKXvwUSF%2BJTug1i%2BQ7QAIz0nKix6XTVTH5fMrLNoIixkf4%2Bex29%2FavM6659cwkh8z6YN4W5A0%2B3onYsrQI%2F2pw9oWJx1Nd399QZRT"}],"group":"cf-nel","max_age":604800} Server: cloudflare Transfer-Encoding: chunked alt-svc: h3=":443"; ma=86400, h3-29=":443"; ma=86400 content-security-policy: base-uri 'none'; default-src 'none'; frame-ancestors 'none'; font-src 'self' https://mtd.naraku.cc; img-src 'self' https: data: blob: https://mtd.naraku.cc; style-src 'self' https://mtd.naraku.cc 'nonce-0qN1dzer3t4zyTyWue1qqw=='; media-src 'self' https: data: https://mtd.naraku.cc; frame-src 'self' https:; manifest-src 'self' https://mtd.naraku.cc; connect-src 'self' data: blob: https://mtd.naraku.cc https://mtd.naraku.cc wss://mtd.naraku.cc; script-src 'self' https://mtd.naraku.cc 'wasm-unsafe-eval'; child-src 'self' blob: https://mtd.naraku.cc; worker-src 'self' blob: https://mtd.naraku.cc etag: W/"416f589000111e76eae38f5f7eb69471" permissions-policy: interest-cohort=() referrer-policy: origin strict-transport-security: max-age=15552000; includeSubDomains; preload vary: Accept, Origin x-content-type-options: nosniff x-frame-options: DENY x-request-id: 52b64673-7371-4292-b508-ef26d617f344 x-runtime: 0.023334 x-xss-protection: 0 { "@context": [ "https://www.w3.org/ns/activitystreams", "https://w3id.org/security/v1", { "Curve25519Key": "toot:Curve25519Key", "Device": "toot:Device", "Ed25519Key": "toot:Ed25519Key", "Ed25519Signature": "toot:Ed25519Signature", "Emoji": "toot:Emoji", "EncryptedMessage": "toot:EncryptedMessage", "PropertyValue": "schema:PropertyValue", "alsoKnownAs": { "@id": "as:alsoKnownAs", "@type": "@id" }, "cipherText": "toot:cipherText", "claim": { "@id": "toot:claim", "@type": "@id" }, "deviceId": "toot:deviceId", "devices": { "@id": "toot:devices", "@type": "@id" }, "discoverable": "toot:discoverable", "featured": { "@id": "toot:featured", "@type": "@id" }, "featuredTags": { "@id": "toot:featuredTags", "@type": "@id" }, "fingerprintKey": { "@id": "toot:fingerprintKey", "@type": "@id" }, "focalPoint": { "@container": "@list", "@id": "toot:focalPoint" }, "identityKey": { "@id": "toot:identityKey", "@type": "@id" }, "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", "messageFranking": "toot:messageFranking", "messageType": "toot:messageType", "movedTo": { "@id": "as:movedTo", "@type": "@id" }, "publicKeyBase64": "toot:publicKeyBase64", "schema": "http://schema.org#", "suspended": "toot:suspended", "toot": "http://joinmastodon.org/ns#", "value": "schema:value" } ], "attachment": [ { "name": "Matrix", "type": "PropertyValue", "value": "<span class=\"h-card\"><a href=\"https://mtd.naraku.cc/@naraku\" class=\"u-url mention\">@<span>naraku</span></a></span>:naraku.cc" } ], "devices": "https://mtd.naraku.cc/users/naraku/collections/devices", "discoverable": true, "endpoints": { "sharedInbox": "https://mtd.naraku.cc/inbox" }, "featured": "https://mtd.naraku.cc/users/naraku/collections/featured", "featuredTags": "https://mtd.naraku.cc/users/naraku/collections/tags", "followers": "https://mtd.naraku.cc/users/naraku/followers", "following": "https://mtd.naraku.cc/users/naraku/following", "icon": { "mediaType": "image/jpeg", "type": "Image", "url": "https://mtd.naraku.cc/system/accounts/avatars/109/513/842/758/850/375/original/355c3a2e588b4fba.jpeg" }, "id": "https://mtd.naraku.cc/users/naraku", "image": { "mediaType": "image/jpeg", "type": "Image", "url": "https://mtd.naraku.cc/system/accounts/headers/109/513/842/758/850/375/original/156276bb27f3591e.jpeg" }, "inbox": "https://mtd.naraku.cc/users/naraku/inbox", "manuallyApprovesFollowers": false, "name": "Naraku :mastodon:", "outbox": "https://mtd.naraku.cc/users/naraku/outbox", "preferredUsername": "naraku", "publicKey": { "id": "https://mtd.naraku.cc/users/naraku#main-key", "owner": "https://mtd.naraku.cc/users/naraku", "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArYihLR7VFAiMymGUwkxo\nmMQCaHd9FD59oUfGWGKVCsJ/IkeIbi0+jM5LyeC/QdwCC/ZRgMNvS4zIQgakSRoq\nkiRN3KL3Nv9Uqo9bjC21/H6bgPiZ1aEmck9sZgN0Polxwa3SPeJ08wY8AOWPnGrg\n0kin8+7D1pkemFSfJSJSjPvc9PrKOBCVdbF74haHA99LeHo6GO75P4iLHQnlw565\nVNrUnbtH52Bcoeavrt0SDdQX239z1YqmRxAuApYlg1l4Hy/+hpmUAoCQJs3ogsjF\nTCjo1jpHgcnKRHn3gLL3o7m4+SpeUuXtFOnsXVnYujpnY0p1ejPx8gwAXcI8kwNI\ngQIDAQAB\n-----END PUBLIC KEY-----\n" }, "published": "2022-12-14T00:00:00Z", "summary": "<p>naraku.cc Admin :verify:</p><p>Stand with Ukraine! 🇺🇦 <br />Stand with Democracy!</p>", "tag": [ { "icon": { "mediaType": "image/gif", "type": "Image", "url": "https://mtd.naraku.cc/system/custom_emojis/images/2022/000/000/973/original/20775f75cba35af7.gif" }, "id": "https://mtd.naraku.cc/emojis/973", "name": ":verify:", "type": "Emoji", "updated": "2022-12-17T17:26:50Z" }, { "icon": { "mediaType": "image/png", "type": "Image", "url": "https://mtd.naraku.cc/system/custom_emojis/images/2022/000/000/099/original/56fceeecb032309c.png" }, "id": "https://mtd.naraku.cc/emojis/99", "name": ":mastodon:", "type": "Emoji", "updated": "2022-12-17T10:45:48Z" } ], "type": "Person", "url": "https://mtd.naraku.cc/@naraku" }
ALTERNATE_DOMAINS
指向该服务器的其它域名。如果有多个域名指向 Mastodon 服务器,配置 ALTERNATE_DOMAINS
允许 Fediverse 服务通过其它域名发现帐户。可配置多个域名,域名之间使用逗号隔开,如 foo.com,bar.com
。
从实现上讲对于列入 ALTERNATE_DOMAINS
的域名,Mastodon 将响应来自这些域名的 WebFinger 查询请求,故其它实例可通过相应的后缀查找到原始帐户。
Mastodon v3.4.0 以后版本 [1] ,出于安全性考量,Puma 只响应 Host
为 LOCAL_DOMAIN
、WEB_DOMAIN
、ALTERNATE_DOMAINS
的 HTTP 请求。对于其它 Host 一律近回 403 Forbidden
响应。
STREAMING_API_BASE_URL
设置 STREAMING_API_BASE_URL
可将 streaming API 部署于不同域名或不同子域名。这可能有助于提高 streaming API 的性能。
示例值:wss://streaming.example.com
。
CDN_HOST
你可以通过设置 CDN_HOST
将静态文件(logos,emojis,CSS,JS 等等等)托管于独立域名,如CDN(内容分发网络,Content Delivery Network),这将降低用户加载时间。
示例值:https://assets.example.com
。
S3_ALIAS_HOST
类似于 CDN_HOST
,设置 S3_ALIAS_HOST
可以将用户上传内容托管至一独立域名。
示例值: files.example.com
。
域名注意事项
如前所述,
Mastodon域名不但是你的用户访问你服务器的方式,更是你的实例和你的用户在联邦宇宙中的身份标识。是后者而不是前者决定了权威域名无法更改。
如果你使用 masto.host 这类的全托管服务,其可能会为你的实例提供 masto.host 子域。但千万注意,实例的域名一定要自行注册,不要使用全托管服务商为你提供的下属子域。 现在设想这样一个场景,masto.host 宣布下个月要大幅提高托管服务收费,因为太贵了,你不再想使用masto.host托管服务了。
如果你使用的是自己的域名,那么很简单,只需要导出数据库、导出媒体文件、导出应用密钥,然后使用这些东西转移到另一家托管服务商或自己托管服务器,然后把域名指向新托管商或新服务器就OK了。
但如果你使用的是 masto.host 的域名,那么你就面临这样一个窘境,你的实例域名是属于masto.host所有,而不是你自己所有,你现在不使用masto.host的服务了,masto.host自然没有义务为你提供域名。由于Mastodon域名一旦确定便不能被更改,如果masto.host不为你提供域名,那你的实例就只能下线。这时如果你实例已经积累了相当用户,又同时希望能继续运行,那你就只有忍受masto.host提价这一个选择,即使价格再贵。
实际操作篇
在不同域名托管 Mastodon 服务
添加反代域名
大致步骤:
组网(同一台机器的情况下可省略)
修改环境变量,将 Puma 监听地址改为
0.0.0.0
(同一台机器的情况下可省略)同步
/home/mastodon/live/public
(同一台机器的情况下可省略)将反代域名添加至
ALTERNATE_DOMAINS
创建 MITM 代理,修改 websocket 流中的
S3_ALIAS_HOST
(可选)- 修改 nginx 配置
- 未设置
STREAMING_API_BASE_URL
、CDN_HOST
、S3_ALIAS_HOST
将反代域名添加至
server_name
中重新生成证书
- 未设置
- 需修改
STREAMING_API_BASE_URL
、CDN_HOST
、S3_ALIAS_HOST
复制创建新一份配置文件
修改
proxy_cache_path
添加
proxy_set_header Accept-Encoding identity;
替换
STREAMING_API_BASE_URL
(可选)替换
CDN_HOST
(可选)替换
S3_ALIAS_HOST
(可选)重新生成证书
- 需修改
修改 nginx 配置的
/api/v1/streaming
部分(可选)重载 nginx 配置
# 同一机器需删去该部分 map $http_upgrade $connection_upgrade { default upgrade; '' close; } # 同一机器需修改名称 upstream backend { server 127.0.0.1:3000 fail_timeout=0; } # 同一机器需修改名称,根据需求将 stream API 上游地址修改为 MITM 代理 upstream streaming { server 127.0.0.1:4000 fail_timeout=0; } # 同一机器需删去该部分 proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=CACHE:10m inactive=7d max_size=1g; server { listen 80; listen [::]:80; # 修改或添加 server_name server_name example.com; # 根据需求修改 root,同一机器保持原状即可 root /home/mastodon/live/public; location /.well-known/acme-challenge/ { allow all; } location / { return 301 https://$host$request_uri; } } server { listen 443 ssl http2; listen [::]:443 ssl http2; # 修改或添加 server_name server_name example.com; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!MEDIUM:!LOW:!aNULL:!NULL:!SHA; ssl_prefer_server_ciphers on; ssl_session_cache shared:SSL:10m; ssl_session_tickets off; # Uncomment these lines once you acquire a certificate: # ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; # ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; keepalive_timeout 70; sendfile on; client_max_body_size 80m; # 根据需求修改 root,同一机器保持原状即可 root /home/mastodon/live/public; gzip on; gzip_disable "msie6"; gzip_vary on; gzip_proxied any; gzip_comp_level 6; gzip_buffers 16 8k; gzip_http_version 1.1; gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript image/svg+xml image/x-icon; location / { try_files $uri @proxy; } # If Docker is used for deployment and Rails serves static files, # then needed must replace line `try_files $uri =404;` with `try_files $uri @proxy;`. location = /sw.js { add_header Cache-Control "public, max-age=604800, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; try_files $uri =404; } location ~ ^/assets/ { add_header Cache-Control "public, max-age=2419200, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; try_files $uri =404; } location ~ ^/avatars/ { add_header Cache-Control "public, max-age=2419200, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; try_files $uri =404; } location ~ ^/emoji/ { add_header Cache-Control "public, max-age=2419200, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; try_files $uri =404; } location ~ ^/headers/ { add_header Cache-Control "public, max-age=2419200, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; try_files $uri =404; } location ~ ^/packs/ { add_header Cache-Control "public, max-age=2419200, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; try_files $uri =404; } location ~ ^/shortcuts/ { add_header Cache-Control "public, max-age=2419200, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; try_files $uri =404; } location ~ ^/sounds/ { add_header Cache-Control "public, max-age=2419200, must-revalidate"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; try_files $uri =404; } location ~ ^/system/ { add_header Cache-Control "public, max-age=2419200, immutable"; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; try_files $uri =404; } location ^~ /api/v1/streaming { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Proxy ""; proxy_pass http://streaming; proxy_buffering off; proxy_redirect off; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; add_header Strict-Transport-Security "max-age=63072000; includeSubDomains"; tcp_nodelay on; } location @proxy { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Proxy ""; proxy_pass_header Server; proxy_pass http://backend; proxy_buffering on; proxy_redirect off; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; #### 新增部分开始 ### # 向上游请求明文,nginx 无法替换压缩内容 proxy_set_header Accept-Encoding identity; # 字符串只进行一次替换,即只替换第一个被匹配的字符串。这里关闭。 sub_filter_once off; #替换的请求类型,增加 application/json 。 sub_filter_types application/json; # 替换 STREAMING_API_BASE_URL (按需) sub_filter wss://example.com wss://example.org; # 替换 CDN_HOST (按需) sub_filter https://cdn.example.com https://cdn.example.org; # 替换 S3_ALIAS_HOST (按需) sub_filter https://img.example.com https://img.example.org; # 替捣 missing.png (按需) sub_filter https://example.com/avatars/original/missing.png https://example.org/avatars/original/missing.png; sub_filter https://example.com/headers/original/missing.png https://example.org/headers/original/missing.png; ### 新增部分结束 ### proxy_cache CACHE; proxy_cache_valid 200 7d; proxy_cache_valid 410 24h; proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504; add_header X-Cached $upstream_cache_status; tcp_nodelay on; } error_page 404 500 501 502 503 504 /500.html; }
websocket MITM 代理
安装 mitmproxy
import re from mitmproxy import ctx def websocket_message(flow): # get the latest message message = flow.messages[-1] if message.from_client: ctx.log.info("Client sent a message: {}".format(message.content)) else: ctx.log.info("Server sent a message: {}".format(message.content)) # manipulate the message content message.content = re.sub("https://img\.example\.com", "https://img.example.org", message.content) if 'FOOBAR' in message.content: # kill the message and not send it to the other endpoint message.kill()
# /etc/systemd/system/mitm-mastodon-websocket.service [Unit] Description=Mastodon Mitm push websocket After=network.target Wants=network.target [Service] Type=simple User=www-data Slice=system-mitm.slice ExecStart=/usr/bin/mitmdump --listen-host 127.0.0.1 -p 4444 -s /opt/mastodon_websocket_messages.py --mode reverse:http://127.0.0.1:4000 --set keep_host_header --quiet Restart=on-failure [Install] WantedBy=multi-user.target