最小权限与 Secrets 基础
"最小权限"不是口号,而是让失误成本变小。哪怕有人拿到了一个账号,也不应该拿到整台机器。
权限隔离模型
graph TD
A[运维工程师 ubuntu] -->|sudo| B[系统管理操作]
C[应用进程 app user] --> D[/opt/myapp/ 仅可读]
C --> E[/etc/myapp/myapp.env 仅可读]
F[数据库 postgres user] --> G[数据库文件]
H[部署脚本 deploy user] --> I[/opt/myapp/releases/ 可写]
J[Secret 文件] -->|chmod 640, chown root:app| C
应用专用用户配置
# 创建无 shell 的应用专用用户(不允许直接登录)
sudo useradd --system --shell /usr/sbin/nologin --create-home --home-dir /opt/myapp app
# 应用目录属于 root,但 app 可读
sudo chown -R root:app /opt/myapp/current
sudo chmod -R 750 /opt/myapp/current
# 日志目录属于 app(应用需要写入)
sudo mkdir -p /var/log/myapp
sudo chown app:app /var/log/myapp
sudo chmod 755 /var/log/myapp
Secrets 文件管理
标准目录结构
/etc/myapp/
├── myapp.env # 环境变量(DB 密码、API 密钥等)
└── myapp.secret.key # 加密密钥(如有)
创建和保护
# 创建 Secrets 目录
sudo mkdir -p /etc/myapp
sudo chown root:app /etc/myapp
sudo chmod 750 /etc/myapp
# 创建环境变量文件
sudo tee /etc/myapp/myapp.env > /dev/null <<'EOF'
DATABASE_URL=postgresql://app:yourpassword@localhost:5432/myapp
REDIS_URL=redis://localhost:6379
SECRET_KEY=your-secret-key-here
APP_ENV=production
PORT=3000
EOF
# 设置权限:只有 root 和 app 组可读
sudo chown root:app /etc/myapp/myapp.env
sudo chmod 640 /etc/myapp/myapp.env
# 验证
ls -la /etc/myapp/myapp.env
# -rw-r----- 1 root app ... /etc/myapp/myapp.env
在 systemd 中引用
[Service]
EnvironmentFile=/etc/myapp/myapp.env
# 变量自动注入,不出现在进程命令行(ps aux 不可见)
数据库最小权限
-- PostgreSQL:为应用创建只有所需权限的数据库用户
CREATE USER app_user WITH PASSWORD 'secure-password';
CREATE DATABASE myapp OWNER app_user;
-- 如果 app 只读(报表服务)
GRANT SELECT ON ALL TABLES IN SCHEMA public TO app_user;
-- 不要给 SUPERUSER 或 CREATEDB 权限
-- 不要用 postgres 超级用户连接应用
防止 Secrets 进入 Git
# .gitignore(根目录)
.env
.env.*
*.env
*.secret
secrets/
config/secrets.yml
# 检查是否已有 Secrets 泄漏到提交历史
git log --all --full-history -- "**/.env*"
git grep -l "password\|secret\|api_key" $(git rev-list --all)
# 如果已经提交了 Secret,必须立即轮转密钥(修改密码/API Key)
# 历史中的 Secret 视为已泄漏,不能通过删除提交来"撤回"
sudo 最小权限
# 查看当前 sudo 权限
sudo -l
# 为部署用户只授权特定命令(而非全部 sudo)
# 编辑 /etc/sudoers.d/deploy(用 visudo 编辑)
sudo visudo -f /etc/sudoers.d/deploy
# /etc/sudoers.d/deploy
# deploy 用户只能重启 myapp 服务,不能做其他 root 操作
deploy ALL=(ALL) NOPASSWD: /bin/systemctl restart myapp, /bin/systemctl status myapp
权限检查清单(操作对照表)
| 对象 | 推荐权限 | 命令 |
|---|---|---|
| 应用代码目录 | root:app 750 | chown root:app /opt/myapp; chmod 750 /opt/myapp |
| Secrets 文件 | root:app 640 | chown root:app /etc/myapp/myapp.env; chmod 640 /etc/myapp/myapp.env |
| 日志目录 | app:app 755 | chown app:app /var/log/myapp; chmod 755 /var/log/myapp |
| SSH 私钥 | user:user 600 | chmod 600 ~/.ssh/id_ed25519 |
| SSH authorized_keys | user:user 600 | chmod 600 ~/.ssh/authorized_keys |
常见误区
| 误区 | 正确做法 |
|---|---|
| 把数据库密码写进 Git | 用 .gitignore 排除,用 /etc/myapp/ 存储 |
所有服务都用同一个 .env 文件 | 每个应用有独立的 Secret 文件,权限隔离 |
| 给应用用户完整 sudo | 只授权 systemctl restart myapp 等最小必要命令 |
| Secret 已泄漏到 Git 历史,删掉就行 | 历史中的 Secret 视为公开,必须立即轮转密钥 |
本节执行清单
- [ ] 为应用创建专用 system user(
--system --shell /usr/sbin/nologin) - [ ] 将 Secrets 放入
/etc/myapp/myapp.env,权限设为640 root:app - [ ] 在
.gitignore中排除所有.env*文件 - [ ] 检查 Git 历史是否已有 Secret 泄漏
- [ ] 审视哪些用户不需要 sudo,或只需要最小 sudo 命令
下一节:Fail2ban、自动更新与审计——继续做机器层面的基础防护。