Sekrety poza repozytorium — .env i config.php

🎯 Po tej lekcji będziesz przechowywać hasła i klucze poza repozytorium gitowym, używać .gitignore do ich blokowania i wczytywać konfigurację bezpiecznie do kodu PHP.

W Module 8 widziałeś połączenie PDO z wpisanym na stałe hasłem do bazy danych:

<?php
// ❌ ANTY-PRZYKŁAD — tak NIE rób!
$pdo = new PDO('mysql:host=localhost;dbname=sklep', 'root', 'TajneHaslo123');

To hasło teraz żyje w historii gita na zawsze. Każdy kto sklonuje repo — kolega, rekruter, atakujący — je zobaczy. Ta lekcja nauczy cię właściwego podejścia.

Problem: sekrety w kodzie

Dlaczego historia gita jest niebezpieczna

Git zapamiętuje każdy commit. Nawet jeśli usuniesz hasło i zrobisz nowy commit, nadal można je znaleźć:

git log --all --full-history  # lista commitów
git show <stary-commit>:db.php  # pokaż plik sprzed usunięcia hasła

Jeśli repo jest publiczne na GitHubie, boty skanujące wyciekłe sekrety znajdą je w kilka sekund od git push. Atakujący automatycznie rotuje w tym czasie hasła i klucze API.

Co jest sekretem?

  • Hasło do bazy danych (DB_PASS)
  • Klucze API (Stripe, Mailchimp, SendGrid…)
  • Klucz szyfrowania sesji / JWT secret
  • Dane SMTP (login + hasło poczty)
  • Wszelkie tokeny dostępu do zewnętrznych usług

Rozwiązanie 1: plik .env i .gitignore

Najpopularniejsze podejście — plik .env w katalogu głównym projektu (nigdy w public/):

# .env — NIGDY nie commituj tego pliku!
DB_HOST=localhost
DB_NAME=zadania_db
DB_USER=appuser
DB_PASS=TajneHasloDoZmiany

Zablokuj go w .gitignore:

# .gitignore
.env
*.log
⚠️ Sprawdź git status: Po dodaniu .env do .gitignore uruchom git status i upewnij się, że .env NIE pojawia się na liście. Jeśli był już śledzony wcześniej, musisz go odśledzić przez git rm --cached .env.

Plik .env.example

Commituj do repo pusty szablon — tylko nazwy zmiennych, bez wartości:

# .env.example — commituj! To dokumentacja dla nowych developerów.
DB_HOST=localhost
DB_NAME=
DB_USER=
DB_PASS=
APP_ENV=development

Nowy developer kopiuje: cp .env.example .env i wpisuje swoje dane.

Wczytywanie konfiguracji — bez zewnętrznych bibliotek

PHP ma wbudowaną funkcję parse_ini_file, która świetnie nadaje się do wczytania prostego pliku .env:

<?php
declare(strict_types=1);

// config.php — plik wczytywany raz na starcie aplikacji

$envSciezka = dirname(__DIR__) . '/.env'; // katalog nad public/

if (!file_exists($envSciezka)) {
    throw new RuntimeException(
        'Brak pliku .env. Skopiuj .env.example i uzupełnij dane.'
    );
}

$config = parse_ini_file($envSciezka);

if ($config === false) {
    throw new RuntimeException('Nie można odczytać pliku .env.');
}

// Użycie:
// $config['DB_HOST'], $config['DB_NAME'], ...

Połączenie PDO z konfiguracją z .env

<?php
declare(strict_types=1);

require_once 'config.php'; // ładuje $config

$pdo = new PDO(
    dsn: 'mysql:host=' . $config['DB_HOST'] . ';dbname=' . $config['DB_NAME'] . ';charset=utf8mb4',
    username: $config['DB_USER'],
    password: $config['DB_PASS'],
    options: [
        PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES   => false,
    ],
);

Hasło zostało w .env, nie w kodzie. Kod możesz commitować.

Rozwiązanie 2: config.php poza public/

Inny popularny wzorzec — osobny plik konfiguracyjny poza katalogiem serwera webowego:

projekt/
├── public/           ← dostępne z internetu (DocumentRoot serwera)
│   └── index.php
├── src/
├── config.php        ← poza public/, nie dostępne przez HTTP
└── .env              ← poza public/, w .gitignore
<?php
// config.php — poza public/, NIE commitujemy tego pliku
declare(strict_types=1);

return [
    'db_host' => 'localhost',
    'db_name' => 'zadania_db',
    'db_user' => 'appuser',
    'db_pass' => 'TajneHaslo',  // hasło tutaj, nie w repo
];
<?php
// public/index.php
declare(strict_types=1);

$config = require dirname(__DIR__) . '/config.php'; // poza public/
// $config['db_pass'] jest teraz dostępne

Dodaj do .gitignore:

config.php
.env

Biblioteka phpdotenv (Moduł 7)

W Module 7 zainstalowałeś Composera. Popularna biblioteka vlucas/phpdotenv automatyzuje wczytywanie .env:

composer require vlucas/phpdotenv
<?php
declare(strict_types=1);

$dotenv = Dotenv\Dotenv::createImmutable(dirname(__DIR__));
$dotenv->load();

// Zmienne dostępne przez $_ENV lub getenv():
$dbPass = $_ENV['DB_PASS'];

phpdotenv oferuje też walidację — sprawdza, czy wymagane zmienne są ustawione:

$dotenv->required(['DB_HOST', 'DB_NAME', 'DB_USER', 'DB_PASS']);
💡 Środowisko produkcyjne: Na serwerze produkcyjnym najlepiej w ogóle nie używać pliku .env — zamiast tego ustaw zmienne środowiskowe bezpośrednio w systemie (zmienne środowiskowe systemu operacyjnego, panel hostingowy, Docker secrets, Kubernetes secrets). Plik .env to wygoda dla developmentu lokalnego.

Lista kontrolna bezpieczeństwa sekretów

  • [ ] .env jest w .gitignore
  • [ ] .env.example jest w repo (bez wartości)
  • [ ] Hasła i klucze API nie ma w żadnym pliku PHP w repo
  • [ ] git log --all nie zawiera haseł w historii
  • [ ] Plik konfiguracyjny leży poza katalogiem public/
  • [ ] Serwer produkcyjny używa zmiennych środowiskowych lub secrets managera

Podsumowanie

| Złe nawyki | Dobre nawyki | |---|---| | Hasło w kodzie PHP | Hasło w .env lub config.php poza repo | | Żaden .gitignore | .env zawsze w .gitignore | | Hardkodowane klucze API | Klucze przez $_ENV['API_KEY'] lub getenv() | | Jeden plik konfiguracji dla dev i prod | Osobne .env dla dev i prod |

W kolejnych dwóch lekcjach zbudujesz projekt końcowy — aplikację „Moje zadania" z kontami użytkowników, łącząc wszystko czego się nauczyłeś.

Sprawdź się

Dlaczego nie wolno trzymać hasła do bazy danych bezpośrednio w kodzie PHP w repozytorium?

Jak sprawdzić że plik .env jest zablokowany przed trafieniem do repozytorium git?

Czym jest plik .env.example (albo .env.dist)?

Ćwiczenie

Stwórz plik .env z przykładową konfiguracją bazy danych (DB_HOST, DB_NAME, DB_USER, DB_PASS). Dodaj .env do .gitignore. Napisz plik config.php, który wczytuje .env przez parse_ini_file() i zwraca tablicę konfiguracji. Wyświetl nazwę bazy, ukrywając hasło (pokaż tylko '***').

Pokaż rozwiązanie
# Plik .env:
DB_HOST=localhost
DB_NAME=moja_baza
DB_USER=root
DB_PASS=TajneHaslo123

# Plik .gitignore:
.env

<?php
// config.php
declare(strict_types=1);

$sciezkaEnv = dirname(__DIR__) . '/.env';

if (!file_exists($sciezkaEnv)) {
    throw new RuntimeException('Brak pliku .env — skopiuj .env.example i uzupełnij dane.');
}

$config = parse_ini_file($sciezkaEnv);

if ($config === false) {
    throw new RuntimeException('Nie można odczytać pliku .env.');
}

// Użycie:
echo 'Baza: ' . $config['DB_NAME'] . PHP_EOL;   // wynik: Baza: moja_baza
echo 'Hasło: ***' . PHP_EOL;                      // wynik: Hasło: ***