V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
0o0O0o0O0o
V2EX  ›  分享创造

vaultwarden 备份思路之再也不改版

  •  
  •   0o0O0o0O0o · 189 天前 · 2569 次点击
    这是一个创建于 189 天前的主题,其中的信息可能已经有所发展或是发生改变。

    vaultwarden 用了几年,备份方案翻来覆去折腾了好多种,这次利用 nginx syslog 实现“精确”的备份:

    log_format json-log escape=json '{"status":$status,"request_method":"$request_method","request_uri":"$request_uri"}';
    
    server {
        location / {
            # ...
    
            proxy_redirect off;
            proxy_pass http://vaultwarden;
            proxy_http_version 1.1;
    
            access_log syslog:server=syslog-server:50333,facility=local7,tag=nginx,severity=info,nohostname json-log;
        }
    }
    

    这样 syslog-server:50333 就可以收到完成的请求,可以拿到 status request_method request_uri 等信息,如此只需要实现个简单的 syslog server ,便可以根据这些信息来决定是否调用备份:例如所有 GET 请求都无需关心,因为对数据库没有更改;例如我不关心登录、注册、设备、二次验证之类的变更,则 ^/identity/accounts/prelogin ^/identity/connect/token .*/devices/.* .*/two-factor/.* 等请求也可以略过;例如我不使用 sends ,.*/sends/.* 也可以忽略。

    bitwarden 客户端让每一次更改都必须对应 vaultwarden 数据库的更改,这个备份方案理论上也可以确保对自己有价值的变更都被备份。

    部署运行了一周,我很喜欢这个方案

    诸位觉得如何

    第 1 条附言  ·  188 天前

    很认同 #13 #15 提到的问题,所以从 access_log syslog server 改成实现一个简单的 reverse proxy,得益于 Go 核心库就有 https://pkg.go.dev/net/http/httputil#ReverseProxy ,这样一个简单实现的代码量很少,犯错的机会也就很少,维护时只需要保持 Go 是最新版本即可

    func NewProxy(upstream string) (*httputil.ReverseProxy, error) {
    	u, err := url.Parse(upstream)
    	if err != nil {
    		return nil, err
    	}
    	return httputil.NewSingleHostReverseProxy(u), nil
    }
    
    func ProxyRequestHandler(proxy *httputil.ReverseProxy) func(http.ResponseWriter, *http.Request) {
    	return func(w http.ResponseWriter, r *http.Request) {
    		proxy.ServeHTTP(w, r)
    	}
    }
    
    func main() {
    	proxy, err := NewProxy("http://vaultwarden")
    	if err != nil {
    		panic(err)
    	}
    
    	proxy.ModifyResponse = func(response *http.Response) error {
    		// nil r.Request?
    		log.Println(response.Request.Method, response.Request.URL.Path, response.StatusCode)
    		// ...
    		return nil
    	}
    
    	http.HandleFunc("/", ProxyRequestHandler(proxy))
    	log.Fatal(http.ListenAndServe(":8080", nil))
    }
    
    36 条回复    2024-07-10 20:53:33 +08:00
    hiplon
        1
    hiplon  
       189 天前
    crontab 全局备份方案

    0 3 * * * zip -r /root/wardendata.zip /root/wardendata

    5 3 * * * scp /root/wardendata.zip [email protected]:/home/xxx/.
    Xusually
        2
    Xusually  
       189 天前 via iPhone
    全量 zip 一下,占空间极小,没必要增加复杂度吧?
    0o0O0o0O0o
        3
    0o0O0o0O0o  
    OP
       189 天前
    @hiplon @Xusually

    我是从 cron 到 inotifywatch 再到如今的 access_log ,这些方案的变更就一个追求,就是我在文中提到的:

    "确保对自己有价值的变更都被备份"

    而没有 cron 那样的时间差,可以避免丢失时间间隔中的变更,因为我认为密码数据很重要,丢失一条可能都很严重,不像笔记备份数据那样我可以接受丢失一部分
    terry0314
        4
    terry0314  
       189 天前
    我的 Vaultwarden 部署在群晖上,完全依赖群晖的备份了,主打一个不操心
    guisheng
        5
    guisheng  
       189 天前 via iPhone
    不理解,首先只要存入数据库中一般客户端都会及时更新,另外即使后台服务没有开启也能使用客户端只是不能新增(这点我感觉没做好,为什么不能先保存能通讯的时候在上传呢)。所以即使我没有及时备份也不会出现数据丢失的情况吧……

    另外全量备份虽然大但是可以删除之前的。我都是整个 docker 挂载目录压缩。
    RiddMa
        6
    RiddMa  
       189 天前
    @0o0O0o0O0o 不明白为什么丢失密码很严重,不是有“忘记密码”功能吗?按天为单位备份感觉完全足够了,又不是天天在加新的密码。要是觉得不靠谱按小时按分钟也行,反正数据库很小。

    真正重要的数据不应该依赖单一的备份手段,要是有完全不能丢的东西,应该在多个介质上重复保存。
    Smilencer
        7
    Smilencer  
       189 天前 via iPhone
    我是靠虚机镜像备份...
    0o0O0o0O0o
        8
    0o0O0o0O0o  
    OP
       189 天前
    @guisheng #5 允许本地保存的话不可避免要处理多客户端的冲突吧,不太懂,但应该挺难妥善处理的? https://www.inkandswitch.com/local-first/
    0o0O0o0O0o
        9
    0o0O0o0O0o  
    OP
       189 天前
    @RiddMa #6

    > 不是有“忘记密码”功能吗?

    确实,但有的东西也许不存在一个"忘记密码"功能

    > 又不是天天在加新的密码

    我倒觉得这恰恰是我追求每次变更都备份的重要支撑。。。变更不频繁所以完全可以每次都备份

    > 真正重要的数据不应该依赖单一的备份手段

    赞同,所以这个方案并不提及具体如何备份

    并且我是在每次变更后备份并发送通知汇报备份结果,变更一定是我和 bitwarden 主动交互,所以如果出现了错误或者没收到通知,我也可以在很短的时间内得知并处理
    greenskinmonster
        10
    greenskinmonster  
       189 天前
    incron (根据文件变化触发命令) -> restic (快照、压缩、加密、去重) 感觉简单也更完善
    0o0O0o0O0o
        11
    0o0O0o0O0o  
    OP
       189 天前
    @greenskinmonster #10 感谢,学到个命令 incron

    我之前用的 inotifywatch ,有些问题,例如我提到的 "登录、注册、设备、二次验证之类的变更",每次登录都会导致数据库文件变化,而我并不需要处理这种变化,access_log 方案就是对此的升级
    pems002
        12
    pems002  
       189 天前
    虽然这种自托管就是自己对自己负责,但是 Bitwarden 或者说 Vaultwarden 并没有推出一个自己真真意义上的备份功能,所以我觉得其实备份的方法就因人而异了,但是楼主这个思路其实我觉得挺好的,比我单纯 cron 一个任务把压缩包传到网盘里的思路要好的多,我再观望一下改天试试看!
    drymonfidelia
        13
    drymonfidelia  
       189 天前
    根据我长期运维 nginx 的经验,nginx 在某些特殊情况(超时、源站返回的时候中断)有概率不写 access log ,还没有定时稳定
    0o0O0o0O0o
        14
    0o0O0o0O0o  
    OP
       189 天前
    @pems002 #12 我有在维护基于此思路的 syslog server 简单实现和 docker compose file ,包括一些容器、Nginx 加固等,不过还很早期,等我继续查阅资料学习和优化一番再来分享
    drymonfidelia
        15
    drymonfidelia  
       189 天前
    另外如果你的 :50333 因为什么原因卡死了,nginx 也不会在他恢复正常的时候重发
    drymonfidelia
        16
    drymonfidelia  
       189 天前
    备份方案还是越简单越不容易出问题,我们以前也搞得这么复杂最后到要用备份的时候发现根本没备份进去,最后花了几十万找人恢复
    0o0O0o0O0o
        17
    0o0O0o0O0o  
    OP
       189 天前
    @drymonfidelia #13 很好的提醒,谢谢。那后面有可能自己实现一个反代,实际上这个简单反代需求也不太需要 Nginx 。
    zhhmax
        18
    zhhmax  
       189 天前
    当你使用客户端的时候,创建或修改的密码必然是先在客户端上保存,然后再推送到服务端。使用 cron 命令定时备份密码文件到云盘,即便是服务器挂了,客户端上已经有最新的数据,也不会有任何密码丢失的情况发生吧,除非是登录网页操作新增密码而不使用任何客户端?。且密码文件体积很小,即便是每五分钟产生一个压缩包,不会有文件量太大没有地方保存的情况出现,如果彻底删了某个密码,也能根据每 5 分钟的备份找回来,很难再想到使用 cron 命令备份还会有什么极端情况发生导致密码同步出问题/丢失密码的情况发生。
    0o0O0o0O0o
        19
    0o0O0o0O0o  
    OP
       189 天前
    @zhhmax #18

    > 当你使用客户端的时候,创建或修改的密码必然是先在客户端上保存,然后再推送到服务端

    https://bitwarden.com/help/using-bitwarden-offline/

    Any unlocked Bitwarden app can be used offline in read-only mode ... you won't be able to make edits to or add vault items

    > 每五分钟产生一个压缩包

    不赞同,我认为和编程一样,自然是越精确越好,对于一些用户来说,为了可能几天才出现一次的变更,何必用这么频繁的备份呢?
    RoccoShi
        20
    RoccoShi  
       189 天前
    这太细了, 我都直接一把梭 cron 每天凌晨停 container 把所有 data volume 打包上传到 google drive 和 onedrive 🤣
    zhhmax
        21
    zhhmax  
       189 天前
    @0o0O0o0O0o #19
    1 、说明你的后端已经挂了,没法新增,何谈丢失?

    2 、每五分钟产生一个压缩包,完全可以凭借一些手段把一段时间内没有变更的压缩包废弃掉,这样的话所有的压缩包也都是密码库产生变动后的文件,照样达成精确备份所有变动的目的。
    ashong
        22
    ashong  
       189 天前
    vaultwarden-backup 打包加密 再上传网盘
    0o0O0o0O0o
        23
    0o0O0o0O0o  
    OP
       189 天前
    @zhhmax #21

    > 凭借一些手段把一段时间内没有变更的压缩包废弃掉

    既然这些手段都是为了达成相同目标,那 cron 相对于这个方案还有哪些优势呢。。。

    目前认可的是这个方案增加了复杂性所以引入了 #13 #15 提到的问题,还有别的什么?这个问题对我来说也算有解

    并且例如 #11 提到的,其实靠各种意义上的 diff ( binary 或是 SQL plain text )都挺难处理到 access_log 方案这样"精确"的
    viWww0vvxmolvY5p
        24
    viWww0vvxmolvY5p  
       189 天前
    没看懂。vaultwarden 新增或修改了账号密码更新会立即同步到所有客户端上,就算服务器停机了,数据库重置了,客户端仍可以打开并导出所有资料,理论上不会丢失。
    zhhmax
        25
    zhhmax  
       189 天前
    @0o0O0o0O0o #23 如果要做到“精确且有必要的备份”,那靠一些简单的 diff 确实比较难实现。我个人更倾向于简单又稳定的 cron ,最大程度确保不会丢数据,可以不用那么精确(在某些极端情况下可能会增加恢复时的工作量)。当然了说这么多没有一定要说服你的意思,讨论嘛,自己用起来舒服的方案那就是最好的方案。
    kuanat
        26
    kuanat  
       189 天前
    稍微跑个题,既然变更历史那么重要,有没有考虑过用 git 和文本文件做密码管理呢?

    使用 git 你可以天然记录变更历史,同时可以任意 self host 或者利用各种公有设施实现分布式备份。

    缺点是文本文件要加密。那就再套一层 gpg 加密好了,git 管理 gpg 加密后的文本文件。

    剩下的事情就是写个 wrapper 把这个操作简化掉。

    如果你认为这个思路合理的话,可以参考 pass - the standard unix password manager

    https://www.passwordstore.org/

    我已经用了十多年了,在各种平台都有开源可自编译的客户端或命令行工具。
    0o0O0o0O0o
        27
    0o0O0o0O0o  
    OP
       188 天前
    @kuanat #26

    > git 可以天然记录变更历史

    我以前就有这么做的,因为 vaultwarden 默认使用 sqlite ,密码相关的数据全部在 sqlite 文件中,我就是通过 .dump 转为 sql 文件加到 .git 里来追踪变更,类似于 https://news.ycombinator.com/item?id=38110286
    0o0O0o0O0o
        28
    0o0O0o0O0o  
    OP
       188 天前
    @zhhmax #25

    > 在某些极端情况下可能会增加恢复时的工作量

    赞同,不过对于自建密码管理器我很重视:

    - 很关注 bitwarden 客户端的 issues ,在更新 vaultwarden 之前都会像这样 https://github.com/dani-garcia/vaultwarden/compare/1.30.4...1.30.5 查看改了什么,所以基本上可以确保不会因为一些 breaking change 丢失数据

    - 定期会做还原演练
    masir
        29
    masir  
       188 天前
    定时任务 每天压缩备份整个目录到 onedrive ,保留 30 份存档,毕竟文件不大。一般密码变化也少,基本够用了。
    Janyd
        30
    Janyd  
       188 天前 via iPhone
    请问能写篇完整的教程吗?
    0o0O0o0O0o
        31
    0o0O0o0O0o  
    OP
       188 天前
    @Janyd #30 见 #14 和 #17 ,等我解决了再来分享
    ysmood
        32
    ysmood  
       187 天前
    @kuanat 可以试试这个,比 gpg 更简单方便 https://github.com/ysmood/whisper
    kuanat
        33
    kuanat  
       187 天前
    @ysmood #32

    哈哈我一直在用你写的 rod ,非常棒!
    0o0O0o0O0o
        34
    0o0O0o0O0o  
    OP
       186 天前   ❤️ 1
    @pems002
    @Janyd

    可以试试了,不过还很早期,确保你有其他的数据备份
    https://github.com/dani-garcia/vaultwarden/discussions/4630
    Janyd
        35
    Janyd  
       182 天前 via iPhone
    @0o0O0o0O0o 谢谢,去试试
    frankilla
        36
    frankilla  
       166 天前
    手动更新,更新到 Bitwarden 里- -
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1178 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 18:12 · PVG 02:12 · LAX 10:12 · JFK 13:12
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.