Typora+minIO:自建图床的搭建全流程与踩坑记录

Home Tips Typora+minIO:自建图床的搭建全流程与踩坑记录

前言

使用Markdown编写的笔记在迁移的过程中,经常会遇到图片链接失效,或无法访问等情况。这种情况下,要么是图片永久丢失了,要么需要手动的一个一个将图片粘贴到新的位置。

为了解决这个问题,有些师傅采用本地存储图片的方法,这样脱离第三方的依赖,对图片数据的完整性保障的的更好,但在云上上传或者迁移笔记时就不是很方便,因为仍然需要一个一个的将图片上传;而有些师傅则是采用Github、Gitlab等第三方免费或者付费的图床平台,自己注册账号使用对应的图床服务。这种方法可能会产生私密性不够、需要额外付费等问题。

本篇文章介绍的方案,是使用开源的minIO对象存储系统来自建图床服务。这样能更好的保障数据安全,并且不需要为服务付费。当然,这种方式是需要一台云服务器作为成本来提供支持的。

一些先决条件

  • 一台云服务器:本篇文章中使用的是一台Ubuntu腾讯云主机
    • docker
    • git
    • nginx
    • cert-bot
  • 一个域名:非必要

环境准备

腾讯云更换Docker镜像源

国内云上使用默认的Docker镜像源可能会出现下载速度太慢的情况,这里推荐替换为腾讯云的镜像源。

/etc/docker/daemon.json 文件中写入以下数据:

{
   "registry-mirrors": [
   "https://mirror.ccs.tencentyun.com"
  ]
}

重启 docker 服务:

sudo systemctl restart docker

搭建步骤

Step 1: 搭建minIO对象存储服务

minIO是一个开源的对象存储系统,也就是我们经常使用的 OSS 服务。该系统由 Golang 编写,对分布式存储的支持也很好。

拉取minIO镜像

docker pull minio/minio

容器启动MinIO实例

mkdir ~/minio #将minio的项目文件统一映射到家目录下的minio文件夹中,方便管理

docker run -p 9000:9000 -p 9001:9001 --name minio -d --restart=always -e "MINIO_ACCESS_KEY=xxx" -e "MINIO_SECRET_KEY=strongPassXADFACasd" -v ~/minio/data:/data -v ~/minio/config:/root/.minio minio/minio server --console-address ":9000" --address ":9001" /data

配置项说明:

  • 端口9000: Web控制台使用端口
  • 端口9001: API使用端口
  • MINIO_ACCESS_KEY=xxx:登录minio服务的用户名
  • MINIO_SECRET_KEY=xxxxxxxx:登录密码
  • /home/data: 宿主机映射目录卷
  • /home/config: 宿主机映射配置文件目录卷

注意事项

  • 将用户名密码替换你自己设置的,另外ACCESS_KEY至少3位,MINIO_SECRET_KEY至少8位,否则容器启动失败,抛出此异常

Step 2: 创建 Bucket,并设置访问权限

安装好 minIO 后,我们首先访问 9000 端口登陆 Web控制台,账号与密码和 Docker 启动时传入的参数相同。

Untitled

登陆成功后,我们需要创建一个 Bucket,之后所有的图片都会自动上传到这个 Bucket 下。

Untitled

为这个 Bucket 创建新增一条匿名访问的配置,这样读取 Bucket 中的图片资源就无需授权了。

Untitled

将策略中的ListBucket配置删除掉,避免Bucket Listing安全问题

Untitled

Untitled

Step 3: 为 minIO API 接口配置TLS加密(必选项)

由于我们后续访问 Bucket 中的图片等资源,都是通过 MinIO 9001 端口的API接口。因此我们需要将该 API 接口配置为通过 HTTPS 访问,以避免 Mixed content 所导致的 HTTPS 网页无法加载图片的问题(HTTPS 网页中加载 HTTP 资源会被浏览器阻拦)。

这里我们选择使用 Nginx 反向代理,来实现这个需求。如果你之前没有为自己的域名配置过TLS证书,这里推荐使用Certbot工具免费为你的域名申请一个由Let’s Encrypt颁发的TLS证书

我们需要在Nginx虚拟主机的目录下(例如: /etc/nginx/vhost ),创建一个配置文件 你的域名.conf ,例如: api.2h0ng.wiki.conf ,加入以下内容。

server {
        listen 80;
        server_name YOUR.DOMAIN.COM;
        return 301 https://$server_name$request_uri;
}

server {
        listen 443 ssl;
        server_name YOUR.DOMAIN.COM;

                # 此段为引入TLS配置,请参考 cert-bot 申请https证书,以及tls nginx配置教程
        include /www/server/nginx/snippets/self-signed.conf;

        proxy_cache_convert_head off;
        location / {
                proxy_set_header Host $host;
                proxy_pass http://localhost:9001;
        }
}

这里需要注意一个点, proxy_cache_convert_head off 这条配置要务必写入。否则 Nginx 会将 HEAD请求转发为GET请求影响minIO的鉴权过程,使图片上传无法完成。

配置文件写好后,找到Nginx可执行文件,执行如下命令,例如: /etc/nginx/sbin/nginx -s reload 来重启Nginx服务,加载新写入的配置。

如果一切配置正常,使用浏览器访问 https://YOUR.DOMAIN.COM/便会自动跳转到 https://YOUR.DOMAIN.COM:9000/

Step 3: 为 minIO Web 控制台接口配置TLS加密(可选项)

如果你是一个完美主义者,想给Web控制台页面也配置一个域名或TLS加密,请切记使用如下的 Nginx 配置:

server {
        listen 80;
        server_name PROTAL.YOUR.DOMAIN.COM;
        return 301 https://$server_name$request_uri;
}
map $http_upgrade $connection_upgrade {
        default upgrade;
        '' close;
}
server {
        listen 443 ssl;
        server_name PROTAL.YOUR.DOMAIN.COM;

                # 此段为引入TLS配置,请参考 cert-bot 申请https证书,以及tls nginx配置教程
        include /www/server/nginx/snippets/self-signed.conf;
        location / {
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Host $host;
                proxy_set_header Connection $connection_upgrade;
                proxy_pass http://localhost:9000;
        }
}

因为在 Web 控制台中浏览存储对象时,获取存储对象内容的接口是通过 Websocket 通信的。此时就需要对 Nginx 进行一些特殊配置。

Untitled

Nginx 作为反向代理服务器使用时,默认会对请求报文中的两个请求头进行修改,一个是 Host,会被修改为被代理的后端服务接口的 Host;另一个则是 Connection 请求头,会被 Nginx 设置为 close转发到后端服务。

而使用 Websocket协议 发送握手请求时,必须要将 Connection 请求头的值,设置为 Upgrade ,来表示请求建立Websocket连接,同时设置了 Upgrade 等请求头包含其它必要信息。

GET / HTTP/1.1
Host: normal-website.com
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: wDqumtseNBJdhkihL6PW7w==
Connection: keep-alive, Upgrade
Cookie: session=KOsEJNuflw4Rd9BDNrVmvwBF9rEijeE2
Upgrade: websocket

所以,为使 WebSocket 连接被成功建立,我们需要让 Nginx 正确地转发 Connection 请求头的值。这里我们利用 Nginx 中的 map 指令(directive)来实现这个需求。如果客户端的请求中存在 Upgrade 请求头,就说明客户端正在请求建立 WebSocket 连接,此时会设置 Connection 请求头的值为 upgrade,否则将会被设置为 Nginx 默认设置的值 close

Step 4: 安装PicGo + MinIO插件

如果你成功的进行到了这一步骤,这意味着我们已经成功地搭建好了我们的自建图床,minIO和Web服务器的搭建与配置就正式完成了。下面只需要将我们的笔记软件和图床相结合,实现图片自动上传的功能即可。这里,我们可以通过 Typora 原生支持的 PicGo 上传工具,来完成这一需求。

PicGo一个用于快速上传图片并获取图片 URL 链接的工具,该工具提供了GUI版本的 PicGo 与命令行版本的 PicGo-Core

PicGo 支持多家图床服务(阿里OSS, 腾讯COS, 七牛图床等),仅需要在 PicGo 中对应的图床配置文件中将 key 等信息填好即可。

Untitled

Typora 原生支持使用 PicGo 和 PicGo-Core 对笔记中的图片进行上传,而笔记中的图片何时通过 PicGo 自动上传,则依赖于 Typora 中上传策略的配置。

Untitled

这里我的配置是,无论笔记中插入的图片来源于本地,还是来源于网络,都会执行上传图片的操作。

选择1:GUI版本的PicGo + MinIO 的安装与配置方式:

Typora仅中文语言模式下支持使用PicGo。因此,选择该方案的同学需要将语言修改为中文,MacOS系统下直接下载安装PicGo.app即可;Windows系统下则会提供一个输入框,输入PicGo可执行文件所在的位置。

Untitled

安装成功后,Mac下记得给个权限

sudo xattr -cr /Applications/PicGo.app

尽管PicGo支持众多图床服务,但minIO并不在列表内。所幸PicGo的插件生态中有社区用户编写好的minIO的上传器。

Untitled

minio插件配置内容如下:

Untitled

  • endPoint 我们所搭建的minIO API接口地址
  • port TCP/IP端口号。可选值,如果是使用HTTP的话,默认值是80;如果使用HTTPS的话,默认值是443
  • useSSL yes代表使用HTTPS;这里我们选择yes
  • accessKey minIO账户用户名
  • secretKey minIO账户密码
  • bucket 图片上传的目的桶名
  • 基础目录 将图片上传到指定目录下, 例如 dir、 dir1/dir2(2.2.0+)
  • 自动归档 yes表示开启,把上传的图片归档到当天日期目录下(2.2.0+)
  • 同名文件 默认: 跳过(2.3.0+)
    • 跳过 检查 minio 中是否存在同名的文件,存在则跳过该文件的上传
    • 覆盖 不进行检查,直接上传
    • 保留两者 检查 minio 中是否存在同名的文件,存在则重命名该文件名再上传(规则:原文件名_repeat_时间戳_随机数.扩展名)
  • 自定义域名 简单的替换 endPoint 的域名(2.3.0+)

以上配置完成后,可使用Typora的上传测试功能查看是否一切配置顺利生效。

Untitled

选择2:PicGo-Core + MinIO 的安装与配置方式

PicGo-Core 是 PicGo 的命令行工具,更加清爽,无需后台运行,即调即用。虽然说 PicGo-Core 是命令行工具,没有GUI,但除了通过编辑配置文件进行配置外,它还提供了一套交互式命令行输入输出的配置方法,减少了配置门槛。

首先通过 npm 安装 PicGo-Core:

npm install picgo -g

Untitled

同 PicGo 一样,下载 minIO 上传器插件:

#下载插件
picgo install minio

Untitled

安装后的插件默认会开启,我们可以通过配置文件查看该状态,如果没有开启,我们可以直接更改配置文件,也可以通过 picgo use plugins 命令交互式进行配置。

#查看配置文件是否已启用minio
cat ~/.picgo/config.json

"picgoPlugins": {
    "picgo-plugin-minio": true
  }

为 minIO 上传器插件进行配置:

#交互配置minio插件
picgo set uploader

Untitled

按照要求依次输入:

  • endPoint 我们所搭建的minIO API接口地址
  • port TCP/IP端口号。可选值,如果是使用HTTP的话,默认值是80;如果使用HTTPS的话,默认值是443。
  • useSSL yes代表使用HTTPS;这里我们选择yes
  • accessKey minIO账户用户名。
  • secretKey minIO账户密码。
  • bucket 图片上传的目的桶名。
  • 存放目录 将图片上传指定目录下, 例如 dir、 dir1/dir2(2.2.0+)
  • 自动归档 yes表示开启,把上传的图片归档到当天日期目录下(2.2.0+)
  • 同名文件 默认: 跳过(2.3.0+)
    • 跳过 检查 minio 中是否存在同名的文件,存在则跳过该文件的上传
    • 覆盖 不进行检查,直接上传
    • 保留两者 检查 minio 中是否存在同名的文件,存在则重命名该文件名再上传(规则:原文件名_repeat_时间戳_随机数.扩展名)
  • 自定义域名 简单的替换 endPoint 的域名(2.3.0+)

最后,我们在 Typora 配置文件中,设置上传服务为 自定义命令 ,输入 picgo upload 命令即可。

Untitled

以上配置完成后,可使用Typora的上传测试功能查看是否一切配置顺利生效。

Untitled

总结

其实看似简单的一套搭建过程中却包含了众多知识点,我在搭建与笔记编写的过程中也遇到和解决了很多以前没有考虑过的问题,由此记录:

  • Mixed Content 加载问题:HTTPS页面中加载HTTP资源会被主流的浏览器们所拦截阻断。这也导致一些渗透测试的场合下,有很多资源接口存在却加载失败,让我们错失了一部分攻击面。这时候,我们可以使用Burp Suite自动化替换返回包中该HTTP链接为HTTPS。或者强制解除这种安全机制
  • Nginx默认的缓存习惯,没有击中缓存时会将HEAD转换成GET请求方法,我们需要将 proxy_cache_convert_head 这条指令(directive)置于OFF。
  • Nginx默认的转发习惯,会将 Connection 设置为 Close,导致转发 WebSocket 握手请求失败等。

彩蛋

该篇文章中所有的图片都已存储于文中所自搭建的 minIO 服务中。

前面在创建与配置 Bucket 的过程中,我们为了保障数据安全已将 Bucket 策略中的 ListBucket 配置项删除,修补了 Bucket Object 遍历漏洞。但是,程序员小钟却一时粗心忘记安全加固另外一个「测试」桶了。你发现这个漏洞点,成功利用并获取小钟的支付宝红包口令吗?

参考资料

https://juejin.cn/post/7138318205512548366

https://blog.miniasp.com/post/2021/02/11/Create-SSL-TLS-certificates-from-LetsEncrypt-using-Certbot

https://www.nginx.com/blog/websocket-nginx/

https://docs.nginx.com/nginx/admin-guide/web-server/reverse-proxy/

http://nginx.org/en/docs/http/ngx_http_core_module.html#var_connection


Related post

发表回复


#footer{ margin: 0 auto; }