mirror of
https://github.com/TronoSfera/backupy-agent.git
synced 2026-05-18 18:13:30 +03:00
fix(agent): env vars BACKUPY_* and accept 64-hex agent keys
Two defects exposed by the first real-world deployment (Flw VPS): - Env tags were BACKUP_* (no Y). Server + dashboard use BACKUPY_*. - agentKeyPattern only matched bkpy_(live|test)_<32 alnum>; server's generateAgentKey emits 64 lowercase hex chars. Accept both.
This commit is contained in:
parent
f2a203d6cc
commit
f9160a7686
9 changed files with 41 additions and 41 deletions
|
|
@ -31,8 +31,8 @@ services:
|
|||
backup-agent:
|
||||
image: backupy/agent:1
|
||||
environment:
|
||||
BACKUP_SERVER_URL: https://api.backupy.ru
|
||||
BACKUP_AGENT_KEY: ${BACKUP_KEY}
|
||||
BACKUPY_SERVER_URL: https://api.backupy.ru
|
||||
BACKUPY_AGENT_KEY: ${BACKUP_KEY}
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- backup-agent-state:/var/lib/backup-agent
|
||||
|
|
@ -46,12 +46,12 @@ volumes:
|
|||
|
||||
| Var | Required | Default | Notes |
|
||||
|---|---|---|---|
|
||||
| `BACKUP_SERVER_URL` | yes | `https://api.backupy.ru` | Must be `https://` (override with `BACKUP_DEV_ALLOW_INSECURE=true` for local dev). |
|
||||
| `BACKUP_AGENT_KEY` | yes | — | Format `bkpy_(live\|test)_<32 alnum>`. Never logged. |
|
||||
| `BACKUP_STATE_DIR` | no | `/var/lib/backup-agent` | Volume-mounted. Must be writable by uid 65532 (distroless `nonroot`). |
|
||||
| `BACKUP_LOG_LEVEL` | no | `info` | `trace`/`debug`/`info`/`warn`/`error`. |
|
||||
| `BACKUP_DOCKER_SOCKET` | no | `/var/run/docker.sock` | Mounted read-only. |
|
||||
| `BACKUP_DEV_ALLOW_INSECURE` | no | `false` | Allows `http://` server URL — dev only. |
|
||||
| `BACKUPY_SERVER_URL` | yes | `https://api.backupy.ru` | Must be `https://` (override with `BACKUPY_DEV_ALLOW_INSECURE=true` for local dev). |
|
||||
| `BACKUPY_AGENT_KEY` | yes | — | Format `bkpy_(live\|test)_<32 alnum>`. Never logged. |
|
||||
| `BACKUPY_STATE_DIR` | no | `/var/lib/backup-agent` | Volume-mounted. Must be writable by uid 65532 (distroless `nonroot`). |
|
||||
| `BACKUPY_LOG_LEVEL` | no | `info` | `trace`/`debug`/`info`/`warn`/`error`. |
|
||||
| `BACKUPY_DOCKER_SOCKET` | no | `/var/run/docker.sock` | Mounted read-only. |
|
||||
| `BACKUPY_DEV_ALLOW_INSECURE` | no | `false` | Allows `http://` server URL — dev only. |
|
||||
|
||||
Everything else (targets, schedules, retention, S3 creds, hooks) comes from
|
||||
the server via `ConfigUpdate` after registration.
|
||||
|
|
@ -127,7 +127,7 @@ Packages that require generated proto code to compile:
|
|||
## Security notes
|
||||
|
||||
- TLS 1.3 to all server endpoints (enforced by `coder/websocket` defaults).
|
||||
- `BACKUP_AGENT_KEY` is never logged (`slog` ReplaceAttr redacts known keys
|
||||
- `BACKUPY_AGENT_KEY` is never logged (`slog` ReplaceAttr redacts known keys
|
||||
defensively; the value is also `json:"-"` in `Config`).
|
||||
- State at rest is AES-256-GCM keyed by HKDF-SHA256 of the agent key.
|
||||
- Docker socket is mounted read-only.
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import (
|
|||
//
|
||||
// Health criteria:
|
||||
//
|
||||
// 1. Required env vars are set (BACKUP_AGENT_KEY / BACKUP_SERVER_URL).
|
||||
// 1. Required env vars are set (BACKUPY_AGENT_KEY / BACKUPY_SERVER_URL).
|
||||
// 2. The state.db file can be opened (validates encryption key + on-disk
|
||||
// integrity).
|
||||
//
|
||||
|
|
|
|||
|
|
@ -20,29 +20,29 @@ import (
|
|||
"github.com/caarlos0/env/v11"
|
||||
)
|
||||
|
||||
// agentKeyPattern enforces the documented BACKUP_AGENT_KEY format
|
||||
// agentKeyPattern enforces the documented BACKUPY_AGENT_KEY format
|
||||
// `bkpy_(live|test)_<32 base62 chars>`. The server issues keys in
|
||||
// this exact shape — see docs/03-agent-spec.md and server task A-09.
|
||||
var agentKeyPattern = regexp.MustCompile(`^bkpy_(live|test)_[A-Za-z0-9]{32}$`)
|
||||
var agentKeyPattern = regexp.MustCompile(`^(?:bkpy_(?:live|test)_[A-Za-z0-9]{32}|[a-f0-9]{64})$`)
|
||||
|
||||
// Config holds all agent bootstrap configuration.
|
||||
type Config struct {
|
||||
ServerURL string `env:"BACKUP_SERVER_URL,required" envDefault:"https://api.backupy.ru"`
|
||||
AgentKey string `env:"BACKUP_AGENT_KEY,required" json:"-"`
|
||||
StateDir string `env:"BACKUP_STATE_DIR" envDefault:"/var/lib/backup-agent"`
|
||||
LogLevel string `env:"BACKUP_LOG_LEVEL" envDefault:"info"`
|
||||
DockerSocket string `env:"BACKUP_DOCKER_SOCKET" envDefault:"/var/run/docker.sock"`
|
||||
ServerURL string `env:"BACKUPY_SERVER_URL,required" envDefault:"https://api.backupy.ru"`
|
||||
AgentKey string `env:"BACKUPY_AGENT_KEY,required" json:"-"`
|
||||
StateDir string `env:"BACKUPY_STATE_DIR" envDefault:"/var/lib/backup-agent"`
|
||||
LogLevel string `env:"BACKUPY_LOG_LEVEL" envDefault:"info"`
|
||||
DockerSocket string `env:"BACKUPY_DOCKER_SOCKET" envDefault:"/var/run/docker.sock"`
|
||||
|
||||
// DevAllowInsecure relaxes the https:// requirement on ServerURL.
|
||||
// Intended for local development against a plaintext server only.
|
||||
DevAllowInsecure bool `env:"BACKUP_DEV_ALLOW_INSECURE" envDefault:"false"`
|
||||
DevAllowInsecure bool `env:"BACKUPY_DEV_ALLOW_INSECURE" envDefault:"false"`
|
||||
|
||||
// MetricsListenAddr is the bind address for the Prometheus
|
||||
// `/metrics` endpoint (D-19). Default is loopback only —
|
||||
// 127.0.0.1:9090. Set to empty to disable the metrics server.
|
||||
// SECURITY: never bind to 0.0.0.0 in production; the endpoint
|
||||
// reveals job IDs and run cadence usable for host fingerprinting.
|
||||
MetricsListenAddr string `env:"BACKUP_METRICS_LISTEN_ADDR" envDefault:"127.0.0.1:9090"`
|
||||
MetricsListenAddr string `env:"BACKUPY_METRICS_LISTEN_ADDR" envDefault:"127.0.0.1:9090"`
|
||||
}
|
||||
|
||||
// Load parses environment variables into a Config and validates them.
|
||||
|
|
@ -62,7 +62,7 @@ func Load() (*Config, error) {
|
|||
// Validate enforces the documented constraints on each field.
|
||||
//
|
||||
// - ServerURL must parse as an https:// URL (http:// only with
|
||||
// BACKUP_DEV_ALLOW_INSECURE=true).
|
||||
// BACKUPY_DEV_ALLOW_INSECURE=true).
|
||||
// - AgentKey must match the canonical `bkpy_(live|test)_…` pattern.
|
||||
// - StateDir must be writable; we test by creating and removing a temp
|
||||
// file so a misconfigured volume mount fails fast at startup.
|
||||
|
|
@ -71,7 +71,7 @@ func (c *Config) Validate() error {
|
|||
return err
|
||||
}
|
||||
if !agentKeyPattern.MatchString(c.AgentKey) {
|
||||
return errors.New("config: BACKUP_AGENT_KEY has invalid format; expected bkpy_(live|test)_<32 alnum>")
|
||||
return errors.New("config: BACKUPY_AGENT_KEY has invalid format; expected 64 hex chars (or legacy bkpy_(live|test)_<32 alnum>)")
|
||||
}
|
||||
if err := validateStateDirWritable(c.StateDir); err != nil {
|
||||
return err
|
||||
|
|
@ -82,27 +82,27 @@ func (c *Config) Validate() error {
|
|||
func validateServerURL(raw string, allowInsecure bool) error {
|
||||
u, err := url.Parse(raw)
|
||||
if err != nil {
|
||||
return fmt.Errorf("config: BACKUP_SERVER_URL is not a valid URL: %w", err)
|
||||
return fmt.Errorf("config: BACKUPY_SERVER_URL is not a valid URL: %w", err)
|
||||
}
|
||||
if u.Host == "" {
|
||||
return errors.New("config: BACKUP_SERVER_URL is missing host")
|
||||
return errors.New("config: BACKUPY_SERVER_URL is missing host")
|
||||
}
|
||||
switch u.Scheme {
|
||||
case "https":
|
||||
return nil
|
||||
case "http":
|
||||
if !allowInsecure {
|
||||
return errors.New("config: BACKUP_SERVER_URL must use https:// (set BACKUP_DEV_ALLOW_INSECURE=true for local dev)")
|
||||
return errors.New("config: BACKUPY_SERVER_URL must use https:// (set BACKUPY_DEV_ALLOW_INSECURE=true for local dev)")
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("config: BACKUP_SERVER_URL has unsupported scheme %q (expected https)", u.Scheme)
|
||||
return fmt.Errorf("config: BACKUPY_SERVER_URL has unsupported scheme %q (expected https)", u.Scheme)
|
||||
}
|
||||
}
|
||||
|
||||
func validateStateDirWritable(dir string) error {
|
||||
if dir == "" {
|
||||
return errors.New("config: BACKUP_STATE_DIR must not be empty")
|
||||
return errors.New("config: BACKUPY_STATE_DIR must not be empty")
|
||||
}
|
||||
// Ensure the directory exists; create it (and parents) if missing.
|
||||
// 0o700 — only the agent UID should ever touch state.
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ func TestValidate_AgentKey(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
} else {
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "BACKUP_AGENT_KEY")
|
||||
require.Contains(t, err.Error(), "BACKUPY_AGENT_KEY")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
// are eventually streamed to the server, so structured form is mandatory).
|
||||
// The dev profile lowers verbosity by disabling source positions.
|
||||
//
|
||||
// BACKUP_AGENT_KEY is never logged — see config.Config which tags it
|
||||
// BACKUPY_AGENT_KEY is never logged — see config.Config which tags it
|
||||
// `json:"-"` and the redactKey helper here for defence-in-depth.
|
||||
package logging
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
// State-at-rest encryption helpers.
|
||||
//
|
||||
// All bucket *values* are wrapped with AES-256-GCM using a key derived from
|
||||
// BACKUP_AGENT_KEY via HKDF-SHA256 (per docs/03-agent-spec.md →
|
||||
// "Шифрование state опционально (key derived из BACKUP_AGENT_KEY)").
|
||||
// BACKUPY_AGENT_KEY via HKDF-SHA256 (per docs/03-agent-spec.md →
|
||||
// "Шифрование state опционально (key derived из BACKUPY_AGENT_KEY)").
|
||||
//
|
||||
// Wire format on disk:
|
||||
//
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// Package state owns the agent's persistent on-disk state — a BoltDB file
|
||||
// at $BACKUP_STATE_DIR/state.db.
|
||||
// at $BACKUPY_STATE_DIR/state.db.
|
||||
//
|
||||
// Buckets:
|
||||
//
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
// "registry" — session metadata: last session_id, server_time, heartbeat.
|
||||
// "logs_buffer" — rate-limited LogEvent buffer when server is unreachable.
|
||||
//
|
||||
// All bucket values are encrypted with AES-256-GCM keyed by HKDF(BACKUP_AGENT_KEY).
|
||||
// All bucket values are encrypted with AES-256-GCM keyed by HKDF(BACKUPY_AGENT_KEY).
|
||||
// See crypto.go for the wire format.
|
||||
//
|
||||
// Concurrency: bbolt serialises write transactions itself, so the Store is
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ type Config struct {
|
|||
Capabilities []string
|
||||
// AllowInsecure permits ws:// / http:// dial schemes when ServerURL
|
||||
// uses one. Production must leave this false — it matches the
|
||||
// agent's BACKUP_DEV_ALLOW_INSECURE bootstrap flag.
|
||||
// agent's BACKUPY_DEV_ALLOW_INSECURE bootstrap flag.
|
||||
AllowInsecure bool
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ services:
|
|||
backup-agent:
|
||||
image: backupservice/agent:latest
|
||||
environment:
|
||||
BACKUP_SERVER_URL: https://backupy.ru
|
||||
BACKUP_AGENT_KEY: ${BACKUP_KEY}
|
||||
BACKUPY_SERVER_URL: https://backupy.ru
|
||||
BACKUPY_AGENT_KEY: ${BACKUP_KEY}
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- backup-agent-state:/var/lib/backup-agent
|
||||
|
|
@ -26,10 +26,10 @@ volumes:
|
|||
|
||||
| Имя | Назначение | Required |
|
||||
|---|---|---|
|
||||
| `BACKUP_SERVER_URL` | Адрес control plane | да |
|
||||
| `BACKUP_AGENT_KEY` | Ключ агента (секрет) | да |
|
||||
| `BACKUP_LOG_LEVEL` | trace/debug/info/warn/error, default info | нет |
|
||||
| `BACKUP_STATE_DIR` | Путь к state, default `/var/lib/backup-agent` | нет |
|
||||
| `BACKUPY_SERVER_URL` | Адрес control plane | да |
|
||||
| `BACKUPY_AGENT_KEY` | Ключ агента (секрет) | да |
|
||||
| `BACKUPY_LOG_LEVEL` | trace/debug/info/warn/error, default info | нет |
|
||||
| `BACKUPY_STATE_DIR` | Путь к state, default `/var/lib/backup-agent` | нет |
|
||||
|
||||
Всё остальное (targets, schedules, S3 creds, retention, hooks) — приходит с сервера через `ConfigUpdate`.
|
||||
|
||||
|
|
@ -46,7 +46,7 @@ volumes:
|
|||
### Persistent state в volume
|
||||
- SQLite или BoltDB в `/var/lib/backup-agent/state.db`.
|
||||
- Хранит: текущий config, очередь jobs, локальные логи, последний known config_version.
|
||||
- Шифрование state опционально (key derived из BACKUP_AGENT_KEY).
|
||||
- Шифрование state опционально (key derived из BACKUPY_AGENT_KEY).
|
||||
|
||||
### WSS-канал
|
||||
- Один long-lived connection на agent_id.
|
||||
|
|
@ -114,7 +114,7 @@ volumes:
|
|||
- TLS 1.3 ко всем endpoint'ам.
|
||||
- Pinning публичного ключа сервера (зашит в бинарь).
|
||||
- Docker socket монтируется read-only.
|
||||
- `BACKUP_AGENT_KEY` никогда не пишется в логи.
|
||||
- `BACKUPY_AGENT_KEY` никогда не пишется в логи.
|
||||
- Локальный state шифруется (опционально включается).
|
||||
- Healthcheck endpoint (если будет) — только на localhost.
|
||||
- Capabilities контейнера: drop ALL.
|
||||
|
|
|
|||
Loading…
Reference in a new issue