今天想经自已的实例加上tor匿名服务,自己亲自动手了才发现 Mastodon 官方文档对于tor匿名服务就是一个坑。

这里假定你已经阅读完官方文档中关于 hidden service 的内容。

现在让我们把目光放到最后一小节。

Gotchas

There are a few things you will need to be aware of. Certain redirects will push your users to https. They will have to manually replace the URL with http to continue.

Various resources, such as images, will still be offered through your regular non-Tor domain. How much of a problem this is will depend greatly on your user’s level of caution.

这一小节翻译成中文就是:

陷阱

你需要注意一些事情。某些重定向会将你的用户跳转至https。他们必须手动把URL替换成http才能继续。

许多的资源,诸如图片,将仍然从常规非Tor域名加载。问题的严重性很大程度上取决于用户的谨慎程度。

读完真是感觉日了狗了,大兄弟呀!怎么能这样搞呢?刚说到关键地方你就不说了,这怎么能行?

没办法只有自己踩坑了。


我接写来的内容,假定你已经完成官方文档所说的设置。我讲解的重点主要是如何解决这此陷阱。

TL;DR

所需软件:

配置文件:

/etc/nginx/sites-available/c.bgme.bid

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

upstream backend {
    server 127.0.0.1:3000 fail_timeout=0;
}

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;
    server_name mastodon.ko6h7dhb3sbygcs6iiodiavjlhomvcjn36wffoyhkgxpavz6u62jm5qd.onion;
    location ~ ^/(emoji|packs|system/accounts/avatars|system/media_attachments/files) {
        add_header Cache-Control "public, max-age=31536000, immutable";
        try_files $uri @proxy;
    }
    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 https;
        proxy_set_header Proxy "";
        proxy_pass_header Server;

        proxy_redirect https://$host http://$host;
        sub_filter 'https://$host/' 'http://$host/';
        sub_filter 'https://bgme.me/' 'http://$host/';
        sub_filter '"streaming_api_base_url":"wss://bgme.me"' '"streaming_api_base_url":"ws://$host"';

        sub_filter 'https://img.bgme.bid/' 'http://img.ko6h7dhb3sbygcs6iiodiavjlhomvcjn36wffoyhkgxpavz6u62jm5qd.onion/';
        proxy_hide_header Content-Security-Policy;
        add_header Content-Security-Policy "base-uri 'none'; default-src 'none'; frame-ancestors 'none'; font-src 'self' https://bgme.me; img-src 'self' https: data: blob: https://bgme.me http://img.ko6h7dhb3sbygcs6iiodiavjlhomvcjn36wffoyhkgxpavz6u62jm5qd.onion; style-src 'self' 'unsafe-inline' https://bgme.me; media-src 'self' https: data: https://bgme.me; frame-src 'self' https:; manifest-src 'self' https://bgme.me; connect-src 'self' data: blob: https://bgme.me https://img.bgme.bid wss://bgme.me; script-src 'self' https://bgme.me; child-src 'self' blob: https://bgme.me; worker-src 'self' blob: https://bgme.me";
        sub_filter_once off;
        sub_filter_types application/json;

        proxy_pass http://unix:/tmp/mitm.sock;
        proxy_buffering on;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        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;
    }
    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 http;
        proxy_set_header Proxy "";

        proxy_pass http://127.0.0.1:8080;
        proxy_buffering off;
        proxy_redirect off;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        tcp_nodelay on;
    }
    include snippets/mastodon.conf;
}

server {
    listen                unix:/tmp/mitm.sock;
    gunzip                on;

    location / {
        proxy_pass_header Server;
        proxy_set_header Host mastodon.ko6h7dhb3sbygcs6iiodiavjlhomvcjn36wffoyhkgxpavz6u62jm5qd.onion;
        proxy_pass http://backend;
    }
}

server {
    listen 80;
    server_name img.ko6h7dhb3sbygcs6iiodiavjlhomvcjn36wffoyhkgxpavz6u62jm5qd.onion;

    location / {
        proxy_cache mastodon_media;
        proxy_cache_revalidate on;
        proxy_buffering on;
        proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
        proxy_cache_background_update on;
        proxy_cache_lock on;
        proxy_cache_valid 1d;
        proxy_cache_valid 404 1h;
        proxy_ignore_headers Cache-Control;
        add_header X-Cached $upstream_cache_status;
        proxy_pass https://s3.us-west-1.wasabisys.com/bgme-mstdn-media/;
    }
}

server {
    listen 80;
    listen [::]:80;
    server_name c.bgme.bid;
    root /var/www/public;
    location / { return 301 https://$host$request_uri; }
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name c.bgme.bid;

    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;

    # Uncomment these lines once you acquire a certificate:
    ssl_certificate     /etc/letsencrypt/live/c.bgme.bid/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/c.bgme.bid/privkey.pem;

    add_header Strict-Transport-Security "max-age=31536000";
    add_header Access-Control-Allow-Origin "https://s3.us-west-1.wasabisys.com";
    location ~ ^/(emoji|packs|system/accounts/avatars|system/media_attachments/files) {
        add_header Cache-Control "public, max-age=31536000, immutable";
        add_header Strict-Transport-Security "max-age=31536000";
        try_files $uri @proxy;
    }
    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 https;
        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;

        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;
        add_header Strict-Transport-Security "max-age=31536000";

        tcp_nodelay on;
    }
    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 https;
        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;

        tcp_nodelay on;
    }
    include snippets/mastodon.conf;
}

/etc/nginx/snippets/mastodon.conf

keepalive_timeout    70;
sendfile             on;
client_max_body_size 80m;

root /var/www/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;

location / {
try_files $uri @proxy;
}

location /sw.js {
add_header Cache-Control "public, max-age=0";
add_header Strict-Transport-Security "max-age=31536000";
try_files $uri @proxy;
}

error_page 500 501 502 503 504 /500.html;

/opt/websocket_messages.py

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\.bgme\.bid/', 'http://img.ko6h7dhb3sbygcs6iiodiavjlhomvcjn36wffoyhkgxpavz6u62jm5qd.onion/', 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
ExecStart=/usr/local/bin/mitmdump --listen-host 127.0.0.1 -p 8080 -s /opt/websocket_messages.py --mode reverse:http://127.0.0.1:4000 --set keep_host_header --quiet
Restart=on-failure

[Install]
WantedBy=multi-user.target

详细解说

移除响应头

需要为 .onion 域名移除 Strict-Transport-Security 响应头:

add_header Strict-Transport-Security "max-age=31536000";

若不移除该行,浏览器将使用https地址访问。

若存在的话,顺便也移除 Access-Control-Allow-Origin

add_header Access-Control-Allow-Origin "https://s3.us-west-1.wasabisys.com";

将302地址从 https 改为 http

由于tor只提供http服务,但是mastodon程序却经常使用302进行跳转,跳转之后的地址一般为https。 这就会导致跳转之后无法访问网页,必须要用户手动将https改为http。

proxy_redirect https://$host http://$host;

替换文件基本设置

替换多次:

sub_filter_once off;

替换 json:

sub_filter_types application/json;

添加本地mitm代理,将 gzip 包解压缩,方便进行替换:

server {
    listen                unix:/tmp/mitm.sock;
    gunzip                on;

    location / {
        proxy_pass_header Server;
        proxy_set_header Host mastodon.ko6h7dhb3sbygcs6iiodiavjlhomvcjn36wffoyhkgxpavz6u62jm5qd.onion;
        proxy_pass http://backend;
    }
}

并将 @proxy 中 proxy_pass 改为该 mitm 代理地址:

proxy_pass http://unix:/tmp/mitm.sock;

将页面中所有普通域名替换为 .onion 域名

将所有https链接改为http

sub_filter 'https://$host/' 'http://$host/';

将所有普通域名替换为 .onion 域名

sub_filter 'https://bgme.me/' 'http://$host/';

将 streaming_api_base_url 替换为 .onion 域名

sub_filter '"streaming_api_base_url":"wss://bgme.me"' '"streaming_api_base_url":"ws://$host"';

将外部资源链接替换为 .onion 域名

因本站只将媒体文件托管理 S3 storage,故此处仅替换媒体文件。若还有静态文件,则与此相类。

反代外部资源:

server {
    listen 80;
    server_name img.ko6h7dhb3sbygcs6iiodiavjlhomvcjn36wffoyhkgxpavz6u62jm5qd.onion;

    location / {
        proxy_cache mastodon_media;
        proxy_cache_revalidate on;
        proxy_buffering on;
        proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
        proxy_cache_background_update on;
        proxy_cache_lock on;
        proxy_cache_valid 1d;
        proxy_cache_valid 404 1h;
        proxy_ignore_headers Cache-Control;
        add_header X-Cached $upstream_cache_status;
        proxy_pass https://s3.us-west-1.wasabisys.com/bgme-mstdn-media/;
    }
}

替换媒体链接:

sub_filter 'https://img.bgme.bid/' 'http://img.ko6h7dhb3sbygcs6iiodiavjlhomvcjn36wffoyhkgxpavz6u62jm5qd.onion/';

将媒体 .onion 域名添加至 img-src

proxy_hide_header Content-Security-Policy;
add_header Content-Security-Policy "base-uri 'none'; default-src 'none'; frame-ancestors 'none'; font-src 'self' https://bgme.me; img-src 'self' https: data: blob: https://bgme.me http://img.ko6h7dhb3sbygcs6iiodiavjlhomvcjn36wffoyhkgxpavz6u62jm5qd.onion; style-src 'self' 'unsafe-inline' https://bgme.me; media-src 'self' https: data: https://bgme.me; frame-src 'self' https:; manifest-src 'self' https://bgme.me; connect-src 'self' data: blob: https://bgme.me https://img.bgme.bid wss://bgme.me; script-src 'self' https://bgme.me; child-src 'self' blob: https://bgme.me; worker-src 'self' blob: https://bgme.me";

替换 websocket 中的媒体文件地址

完成上述设置,大体上的都已经走hidden service。

但是实际使用时会发现,通过 websocket 推送过来的嘟文,媒体文件仍然走外部资源,所以为了隐私着想,必须把这个问题解决了。


首先,安装 mitmproxy。 进入下载页面,下载并安装最新版。

cd /tmp
wget https://snapshots.mitmproxy.org/5.1.1/mitmproxy-5.1.1-linux.tar.gz
wget https://snapshots.mitmproxy.org/5.1.1/pathod-5.1.1-linux.tar.gz
tar -x -C /usr/local/bin -f mitmproxy-5.1.1-linux.tar.gz
tar -x -C /usr/local/bin -f pathod-5.1.1-linux.tar.gz

配置 mitmproxy,以替换媒体地址。

创建 /opt/websocket_messages.py/etc/systemd/system/mitm-mastodon-websocket.service 两文件。

并运行如下命令:

mkdir /var/www/.mitmproxy
chown www-data:wwww-data /var/www/.mitmproxy
systemctl daemon-reload
systemctl start mitm-mastodon-websocket.service
systemctl enable mitm-mastodon-websocket.service

将 nginx 配置文件中 streaming api proxy_pass 改为 mitm 代理地址:

proxy_pass http://127.0.0.1:8080;

至此,所有配置已完成。

重载nginx配置即可。


一点吐嘈:

虽然官方文档中说什么 DRY(Don't repeat yourself),但实际上配置下来,会发现提取 nginx 配置共性至单独一文件,并没有节省多少行数。