【免费下载链接】borg
Deduplicating archiver with compression and authenticated encryption.
项目地址:
https://gitcode.com/gh_mirrors/bo/borg
点击查看 免费下载
本指南以 Borg 仓库中的 docs/deployment/central-backup-server.rst 为基础,完整讲解如何在一台备份服务器上为多个客户端(桌面机、Web 服务器、应用服务器)提供隔离的仓库存储:单 UNIX 用户 + borg serve –restrict-to-path 强制路径限制 + authorized_keys 强制命令,并给出 Ansible 与 Salt 两套可复用的自动化部署配置。读完本文,你将掌握集中式 Borg 备份服务的目录规划、SSH 强制命令限制、客户端仓库初始化方式,以及越权访问时错误信息的排查方法,并理解其背后的源码实现原理。
适用前提:本文方案与 Borg 2 的说明
官方文档明确指出,本示例早于 Borg 2,使用的是传统 ssh:// 传输方式(由 borg serve 提供服务)和 borg init 命令:
- 在 Borg 2 中,ssh:// 传输仅用于 legacy borg 1.x(v1)仓库;
- 对于当前版本的仓库,应改用 rest:// 仓库:Borg 通过 SSH 连接并在远程主机上运行一个 borgstore REST 服务器(即 borg serve –rest);
- 创建仓库应使用 borg repo-create 而非 borg init。
因此,如果部署的是 Borg 1.x 环境,本文的 ssh:// 示例可直接使用;如果使用 Borg 2 及更新版本,请参考仓库中的 hosting-repositories.rst(其中给出了 command="borg serve –rest –restrict-to-repository …",restrict 的现代写法)与 repo-create.rst(例如 borg repo-create –encryption=aes256-ocb)作对应调整。本文后续内容严格遵循原文档,以 ssh:// + borg serve 的经典集中式服务器为例展开。
机器规划(Machines)
本方案涉及多台机器,文档中以各自的完全限定域名(FQDN)来指代:
- 备份服务器:backup01.srv.local
- 客户端:
- John Doe 的桌面机:johndoe.clnt.local
- Web 服务器 01:web01.srv.local
- 应用服务器 01:app01.srv.local
核心设计思想是:所有客户端共享同一个备份服务器的 UNIX 账号,通过 SSH 公钥 + 强制命令实现一对一的路径隔离,而不是为每个客户端创建独立的系统账号。
备份服务器的用户与组(User and group)
仓库服务器应当为所有客户端创建一个单一的 UNIX 用户。文档推荐的用户与组及附加设置如下:
| 用户(User) | backup |
| 组(Group) | backup |
| Shell | /bin/bash(或其他能够运行 borg serve 命令的 shell) |
| 家目录(Home) | /home/backup |
大多数客户端应以 root 用户发起备份,以便捕获完整的用户、组和权限信息(例如备份 /home 目录时)。
目录布局(Folders)
备份服务器上建议采用如下目录结构:
- 用户家目录:/home/backup
- 仓库路径(存储池):/home/backup/repos
- 各客户端的受限路径(/home/backup/repos/<client fqdn>):
- johndoe.clnt.local → /home/backup/repos/johndoe.clnt.local
- web01.srv.local → /home/backup/repos/web01.srv.local
- app01.srv.local → /home/backup/repos/app01.srv.local
即每个客户端在存储池下拥有一个以自己 FQDN 命名的独立子目录,所有仓库都位于该子目录内部。
访问限制:borg serve –restrict-to-path
Borg 通过如下命令把客户端限制在其自身路径内:
borg serve –restrict-to-path /home/backup/repos/<client fqdn>
客户端能够访问 /home/backup/repos/<client fqdn> 内部的任何文件与子目录,但无法访问任何其他目录。如果需要允许某个客户端访问多个相互独立的目录,可以重复传递多个 –restrict-to-path 标志,例如:
borg serve –restrict-to-path /home/backup/repos/<client fqdn> –restrict-to-path /home/backup/repos/<other client fqdn>
这在「多台机器属于同一个人,且此人应能访问自己所有机器的备份」的场景下非常有用。
authorized_keys 中的强制命令
每个客户端只允许一把 SSH 公钥。本例中为 johndoe.clnt.local、web01.srv.local、app01.srv.local 各添加一把密钥,它们以单一 UNIX 账号 backup@backup01.srv.local 访问备份。$HOME/.ssh/authorized_keys 中的每一把密钥都带有强制命令(forced command)与限制项,格式如下:
command="cd /home/backup/repos/<client fqdn>;
borg serve –restrict-to-path /home/backup/repos/<client fqdn>",
restrict <keytype> <key> <host>
注意: 上述文本必须写成单行!
该密钥行添加的选项依次完成三件事:
由于有了 cd 命令,服务器会自动切换当前工作目录,客户端无需知道远程仓库的绝对或相对路径,可以直接通过如下形式访问仓库:
ssh://<user>@<host>/./<repo>
注意: 上述设置会忽略客户端提供的、通常会附加到 borg serve 命令后的所有命令行参数(除少数白名单项外,见下文「SSH_ORIGINAL_COMMAND 白名单机制」)。
源码层面的限制实现
–restrict-to-path 的底层行为可以在源码中得到印证:
- 参数解析位于 serve_cmd.py:–restrict-to-path 使用 action="append",可重复指定多次,帮助文本明确说明「Access to all subdirectories is granted implicitly; PATH does not need to point directly to a repository」(所有子目录隐式可访问,PATH 不必直接指向仓库)。此外还有 –restrict-to-repository PATH,它只允许访问 PATH 处的单个仓库(不包含子目录),且 PATH 处可以是不存在的空目录、允许客户端在那里初始化仓库。
- 路径校验位于 legacy/remote.py 的 RepositoryServer.open():先对路径做 os.path.realpath() 解析,再给路径补上结尾分隔符(os.path.join(path, "")),然后逐条与每个受限路径做带结尾斜杠的前缀匹配(path_with_sep.startswith(restrict_to_path_with_sep))。这样设计是为了避免 –restrict-to-path /foo 误放行 /foobar 这类路径;全部不匹配时抛出 PathNotAllowed。
- PathNotAllowed 异常定义于 helpers/errors.py,相关测试(见 argparsing_test.py 与 serve_cmd_test.py)验证了「允许路径之下的 backend 可通过、路径之外被拒绝」的行为。
客户端操作:初始化仓库与越权示例
初始化仓库
客户端(如 John Doe 的桌面机)初始化 pictures 仓库:
borg init ssh://backup@backup01.srv.local/./pictures
/./ 表示相对于 SSH 登录后的工作目录(即服务器已通过强制命令 cd 进入的 <client fqdn> 目录),这是推荐写法。
也可以使用完整路径(仅作演示,实际不建议使用;服务器会自动切换工作目录到 <client fqdn> 目录):
borg init ssh://backup@backup01.srv.local/home/backup/repos/johndoe.clnt.local/pictures
越权访问被拒绝
当 johndoe.clnt.local 试图访问其限制之外的路径时(例如 John Doe 想把备份写到 web01 的路径下):
borg init ssh://backup@backup01.srv.local/home/backup/repos/web01.srv.local/pictures
会得到如下错误:
~~~ SNIP ~~~
Remote: borg.remote.PathNotAllowed: /home/backup/repos/web01.srv.local/pictures
~~~ SNIP ~~~
Repository path not allowed
这正是 RepositoryServer.open() 中前缀校验失败后抛出 PathNotAllowed、并经远程 RPC 异常重建机制传回客户端的结果。从仓库文档与测试可知,PathNotAllowed 对应退出码 83(见 hosting-repositories.rst 及 serve_cmd_test.py 中对 PathNotAllowed.exit_mcode 的断言)。
用 Ansible 自动化部署
Ansible 负责完成所有系统级操作:添加用户、创建目录、安装与配置软件。原文给出的完整 playbook 如下:
– hosts: backup01.srv.local
vars:
user: backup
group: backup
home: /home/backup
pool: "{{ home }}/repos"
auth_users:
– host: johndoe.clnt.local
key: "{{ lookup('file', '/path/to/keys/johndoe.clnt.local.pub') }}"
– host: web01.clnt.local
key: "{{ lookup('file', '/path/to/keys/web01.clnt.local.pub') }}"
– host: app01.clnt.local
key: "{{ lookup('file', '/path/to/keys/app01.clnt.local.pub') }}"
tasks:
– package: name=borg state=present
– group: name="{{ group }}" state=present
– user: name="{{ user }}" shell=/bin/bash home="{{ home }}" createhome=yes group="{{ group }}" groups= state=present
– file: path="{{ home }}" owner="{{ user }}" group="{{ group }}" mode=0700 state=directory
– file: path="{{ home }}/.ssh" owner="{{ user }}" group="{{ group }}" mode=0700 state=directory
– file: path="{{ pool }}" owner="{{ user }}" group="{{ group }}" mode=0700 state=directory
– authorized_key: user="{{ user }}"
key="{{ item.key }}"
key_options='command="cd {{ pool }}/{{ item.host }};borg serve –restrict-to-path {{ pool }}/{{ item.host }}",restrict'
with_items: "{{ auth_users }}"
– file: path="{{ home }}/.ssh/authorized_keys" owner="{{ user }}" group="{{ group }}" mode=0600 state=file
– file: path="{{ pool }}/{{ item.host }}" owner="{{ user }}" group="{{ group }}" mode=0700 state=directory
with_items: "{{ auth_users }}"
几个要点:
- 通过变量 pool = "{{ home }}/repos" 统一管理存储池路径,auth_users 列表以「主机名 + 公钥文件」的形式声明客户端;
- authorized_key 模块的 key_options 中拼接出与手动配置完全一致的强制命令:command="cd {{ pool }}/{{ item.host }};borg serve –restrict-to-path {{ pool }}/{{ item.host }}",restrict;
- 注意 authorized_keys 与各客户端目录都以 0700/0600 权限收敛,SSH 对这两个位置的权限有严格要求;
- playbook 中 johndoe.clnt.local 的 auth_users 使用了 .clnt,而文档「Machines」一节中为 .clnt,两者在原文中即存在细微不一致,实际使用时请统一为你环境中真实的 FQDN。
用 Salt 自动化部署
以下是与之等价的 Salt 配置,运行于 Debian 系统:
Install borg backup from pip:
pkg.installed:
– pkgs:
– python3
– python3-dev
– python3-pip
– python-virtualenv
– libssl-dev
– openssl
– libacl1-dev
– libacl1
– build-essential
– libfuse-dev
– fuse
– pkg-config
pip.installed:
– pkgs: ["borgbackup"]
– bin_env: /usr/bin/pip3
Setup backup user:
user.present:
– name: backup
– fullname: Backup User
– home: /home/backup
– shell: /bin/bash
# CAUTION!
# If you change the ssh command= option below, it won't necessarily get pushed to the backup
# server correctly unless you delete the ~/.ssh/authorized_keys file and re-create it!
{% for host in backupclients %}
Give backup access to {{host}}:
ssh_auth.present:
– user: backup
– source: salt://conf/ssh-pubkeys/{{host}}-backup.id_ecdsa.pub
– options:
– command="cd /home/backup/repos/{{host}}; borg serve –restrict-to-path /home/backup/repos/{{host}}"
– restrict
{% endfor %}
要点说明:
- Salt 方案在 Debian 上通过 pkg.installed 安装 Python 及构建依赖(含 libfuse-dev/fuse 用于挂载、libacl1-dev/libacl1 用于 ACL 支持),再经 pip.installed 安装 borgbackup 包;
- 使用 user.present 创建 backup 用户(家目录 /home/backup、shell /bin/bash);
- {% for host in backupclients %} 循环为每个客户端从 salt://conf/ssh-pubkeys/{{host}}-backup.id_ecdsa.pub 拉取公钥,并写入与 Ansible 版本相同的强制命令与 restrict 限制;
- 注意注释中的警告:如果修改了 ssh command= 选项,除非先删除 ~/.ssh/authorized_keys 文件再重新生成,否则改动不一定会正确推送到备份服务器(Salt 的 ssh_auth.present 对既有授权行的更新行为所致)。
增强方向(Enhancements)
原文档指出,上述方案是一个简单且有效的配置,但也可以进一步增强,以支持(有限集合的)客户端提供的命令:
- 可以编写一个用于启动 borg serve 的包装脚本(wrapper);
- 或者增强 Borg 自身——通过检测 SSH_ORIGINAL_COMMAND 环境变量来自动识别它是否运行在 SSH 之下。
如果 Borg 内置了 SSH 自动检测,则无需外部包装脚本,也无需部署其他解释器或应用程序。这一点在当前仓库中已经部分实现:borg serve 对 ssh 强制命令有专门支持——当环境变量 SSH_ORIGINAL_COMMAND 存在时,它会忽略客户端命令行中除白名单外的选项,只从该变量取值(详见 serve.rst)。
SSH_ORIGINAL_COMMAND 白名单机制
从仓库文档 serve.rst 可以确认 borg serve 的强制命令细节:
- 客户端发送的原始命令行会通过 SSH_ORIGINAL_COMMAND 传给服务器,borg serve 仅接受其中的受控白名单选项:
- 控制日志级别与调试主题的选项,如 –verbose、–info、–debug、–debug-topic 等;
- –lock-wait:允许客户端控制等待锁的超时时间;
- –backend:允许客户端选择具体要使用的仓库(配合 borg serve –rest 使用);强制命令始终钉住限制——客户端给出的 backend 会被校验是否落在 –restrict-to-path / –restrict-to-repository 允许的范围内。
- 明确不在白名单中的有:–rest(强制命令钉住模式:是当前 rest:// 仓库还是 legacy Borg 1.x 仓库)、–restrict-to-path、–restrict-to-repository、–umask、–permissions。
- 客户端原命令中出现的环境变量(如 BORG_XXX)不会被解释,而是被忽略;如需在 borg serve 侧设置环境变量,必须写在系统级位置(如 /etc/environment)或强制命令本身中。
因此,即便客户端试图在命令行中附加额外的 borg serve 参数,其模式与路径限制也始终由 authorized_keys 中的强制命令决定——这正是本集中式备份方案安全性的核心。
生产环境加固建议
基于原文档并结合作者后续文档与测试,以下加固项强烈建议在部署集中式备份服务器时一并落实:
- 客户端 ~/.ssh/config 或 /etc/ssh/ssh_config 中: Host backupserver
ServerAliveInterval 10
ServerAliveCountMax 30 - 服务器端 /etc/ssh/sshd_config 中: ClientAliveInterval 10
ClientAliveCountMax 30 - 客户端每 10 秒发送一次保活,30 次无响应(约 300 秒)后终止连接,borg serve 随之优雅退出并释放锁;配合 –lock-wait 600 可留出充足等待时间。超时数值可按实际环境调整。
参考
- 本部署方案原文:central-backup-server.rst
- borg serve 用法与 SSH 强制命令白名单:serve.rst
- Borg 2 的仓库托管方案(–rest 模式):hosting-repositories.rst
- 仓库创建命令 borg repo-create:repo-create.rst
- serve 子命令实现与 –restrict-to-path/–restrict-to-repository 解析:serve_cmd.py
- 路径限制前缀校验实现(RepositoryServer.open):legacy/remote.py
- 相关测试:serve_cmd_test.py、argparsing_test.py
赞
【免费下载链接】borg
Deduplicating archiver with compression and authenticated encryption.
项目地址:
https://gitcode.com/gh_mirrors/bo/borg
点击查看 免费下载
相关推荐
Vue右键菜单组件v-contextmenu完全使用指南:打造极致用户体验的终极教程
如何用 Selenium Grid 4 的 relay 功能把多个 Appium 服务器接入同一个 Hub?
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网硕互联帮助中心



评论前必须登录!
注册