diff --git a/.gitignore b/.gitignore index 98398ee..1ef29e7 100644 --- a/.gitignore +++ b/.gitignore @@ -46,8 +46,9 @@ Thumbs.db # Config files config.toml -# Переменные окружения: файл читает сам бинарник при старте, и секрет в нём -# оказывается тем же способом, каким оказывается в конфиге. +# Переменные окружения: сервис их не читает, настройки приезжают из TOML. +# Строка стоит против того, чтобы секрет завёлся здесь руками: из этого файла +# он попадает в git тем же способом, каким попал бы из конфига. .env # Sample and test audio files diff --git a/.golangci.yml b/.golangci.yml index adae1a9..5f1766c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -64,9 +64,8 @@ linters: msg: 'пишем через slog, а не в stdout напрямую (docs/conventions/logging.md)' # Конфигурация приезжает из TOML. Перечислены все способы прочитать # окружение, а не один: `os.Getenv` без соседей обходится `os.LookupEnv` - # одной правкой. Окружение читает только godotenv в точке входа - # `cmd/transcriber` — он кладёт .env в окружение процесса, а не в - # настройки. + # одной правкой. Наш рабочий код окружение не читает вовсе — это + # правило и держит. # # Чего правило не ловит: `fmt.Fprintln(os.Stdout, …)` и # `os.Stdout.WriteString` — первый аргумент по имени функции не судится. diff --git a/cmd/transcriber/main.go b/cmd/transcriber/main.go index abfc362..184075f 100644 --- a/cmd/transcriber/main.go +++ b/cmd/transcriber/main.go @@ -13,7 +13,6 @@ import ( "syscall" "time" - "github.com/joho/godotenv" "github.com/prometheus/client_golang/prometheus/promhttp" ffmpegconv "git.vakhrushev.me/av/transcriber/internal/adapter/converter/ffmpeg" @@ -111,11 +110,6 @@ func run(logger *slog.Logger) error { return err } - // Загружаем переменные окружения из .env файла - if err := godotenv.Load(); err != nil { - logger.Warn("Warning: .env file not found, using system environment variables") - } - db, err := sqliterepo.Open(cfg.Storage.DataDir, sqliterepo.Settings{ BusyTimeoutMs: cfg.Storage.BusyTimeoutMs, ReadConnections: cfg.Storage.ReadConnections, diff --git a/docs/conventions/config.md b/docs/conventions/config.md index 1b4c094..952c4fc 100644 --- a/docs/conventions/config.md +++ b/docs/conventions/config.md @@ -10,15 +10,17 @@ **Механизировано:** запрет `os.Getenv` — `forbidigo` в `.golangci.yml` ([go-linters.md](go-linters.md), «Механизировано»). Он держит правило «настройки -приезжают из TOML»; `godotenv` в `cmd/transcriber` по-прежнему загружает `.env`, но кладёт -его в окружение процесса, а не в настройки приложения. +приезжают из TOML»: наш рабочий код окружение не читает вовсе — читателя `.env` +в `cmd/transcriber` сняли 2026-08-23 вместе с зависимостью. Окружение остаётся +у границы SDK: `aws-sdk-go-v2` в `internal/adapter/recognizer/yandex/s3.go` +зовёт `config.LoadDefaultConfig`, а тот читает `AWS_PROFILE`, `AWS_CA_BUNDLE`, +`AWS_ENDPOINT_URL` и `AWS_ENDPOINT_URL_S3` и смотрит `~/.aws/config`. ## Принципы - **Конфигурация — только TOML.** Переменные окружения для конфигурации **не используем**: окружение наследуется дочерними процессами и видно через `/proc//environ` — для секретов это слабее файла под `0600`. - *Расхождение:* `cmd/transcriber` зовёт `godotenv.Load()` и молча продолжает без файла. - Грузим **один раз при старте** в одну типизированную структуру `Config` (под-структуры по секциям). Дальше по коду читаем только её — чтения файла в прикладном коде нет, только загрузчик `internal/config`. @@ -127,6 +129,16 @@ Ansible из `pet-project-server`). Приложение просто читае текст остаётся как есть — иначе за разборчивость отказа платили бы там, где платить не за что. +**Файл, способный нести секрет, называется в `.gitignore` и в +`.dockerignore`.** Путей наружу у такого файла два, и закрывает +их разное: git держит `.gitignore`, а контекст сборки образа — `.dockerignore`, +потому что docker `.gitignore` не читает. Сегодня в обоих названы `config.toml` +и `.env`. Правило записано прозой и держится чтением: сверка двух списков стала +бы проверкой над проверкой, а такие проект не заводит +([../../CLAUDE.md](../../CLAUDE.md), «Запреты»). До 2026-08-23 парность не +называл ни один документ, и прогон, снимавший мёртвого читателя `.env`, снял +строку с одной стороны — вернуло её ревью. + ## Проверка и остановка на старте Конфиг проверяем **на старте, до приёма трафика**. Негодный конфиг — лог `ERROR` diff --git a/docs/security.md b/docs/security.md index f4e067b..b812fc8 100644 --- a/docs/security.md +++ b/docs/security.md @@ -336,6 +336,17 @@ Storage, оттуда его читает SpeechKit. Третий путь — Всё перечисленное лежит в `config.toml`. Файл в `.gitignore`, на сервер его кладёт Ansible; `gitleaks` на pre-commit смотрит только индекс коммита. +**Строки `.env` в `.gitignore` и в `.dockerignore` стоят без читателя, и снимать +их поэтому нельзя.** Читателя сняли 2026-08-23 вместе с зависимостью +`godotenv`: наш рабочий код окружение не читает, и файл, положенный рядом с +бинарником, ничего не меняет. Барьеры остались против другого — против того, +чтобы секрет завёлся в этом файле руками и уехал из него теми же двумя путями, +какими уехал бы из конфига: в git и в контекст сборки образа. Второй барьер +нужен отдельно от первого: `Dockerfile` копирует корень целиком (`COPY . .`), а +`.gitignore` docker не читает — чужой `.env` лёг бы слоем образа. Инвариант, +ради которого барьеры стоят, — «Секрет не покидает конфиг» из +[../CLAUDE.md](../CLAUDE.md). + Целевой периметр добавляет к списку пять записей, и первая из них — новый вид секрета, которого сегодня в проекте нет вовсе: diff --git a/go.mod b/go.mod index 795d6c3..5ad5305 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,6 @@ require ( github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3 github.com/aws/smithy-go v1.27.7 github.com/google/uuid v1.6.0 - github.com/joho/godotenv v1.5.1 github.com/pressly/goose/v3 v3.27.3 github.com/prometheus/client_golang v1.23.0 github.com/stretchr/testify v1.11.1 diff --git a/go.sum b/go.sum index 07144ea..8fbb37f 100644 --- a/go.sum +++ b/go.sum @@ -61,8 +61,6 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/klauspost/compress v1.19.1 h1:VsB4HPswih7mmZ8WleSFQ75c/Ui1M4trX5oAsJnhSlk= github.com/klauspost/compress v1.19.1/go.mod h1:cwPg85FWrGar70rWktvGQj8/hthj3wpl0PGDogxkrSQ= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=