NovinkaZverejnili sme 50+ Laravel balíkov ako open source
Custom AI apps, agents and automation — Roundly ConsultingRoundly
Všetky balíky
Sentinel for Laravel

Správa kľúčov

Kľúče žijú v pomenovaných kruhoch: default pre pečate, denník a kontrolné body; http pre podpisy správ; vaše vlastné — napríklad kruh financial so silnejšími kľúčmi, ktorý pečať zvolí cez ring(); a kruhy pre MAC (napríklad logs), ktorých tajomstvá zdieľate s vlastnými odosielateľmi (pozri MAC pre vlastné služby). Id kľúča sa hľadá len v jeho kruhu a algoritmus vždy určuje kľúč — nikdy uložený riadok ani prijatý parameter.

DriverOdkiaľ sú kľúčeRotácia
config (kruh default)SENTINEL_KEY_ID / SENTINEL_KEY (+ SENTINEL_PREVIOUS_KEYS pre kľúče len na overovanie)sentinel:key:rotate vypíše nové riadky env
database (kruh http)Riadky sentinel_keys: AES-256-GCM pod kľúčom odvodeným z APP_KEY len pre obálky (nikdy nie aplikačný encrypter), každé pole viazané — upravený, skopírovaný či podstrčený riadok sa odmietnesentinel:key:rotate uloží nový kľúč; starý je potom len na overovanie
chainDrivery v poradí (vyhráva prvý podpisový kľúč)podľa drivera

Algoritmy a materiál

AlgoritmusMateriálPečateHTTP
hmac-sha256Koreňové tajomstvo 32–1024 bajtov; HKDF podkľúče 32 bajtovánoáno (surové zdieľané tajomstvo)
hmac-sha384Koreň ≥ 32 bajtov; podkľúče 48 bajtováno—
hmac-sha512Koreň ≥ 32 bajtov; podkľúče 64 bajtováno—
ed2551964-bajtový tajný kľúč libsodium / 32-bajtový verejný kľúčánoáno
ecdsa-p256-sha256PKCS#8 / SPKI PEM, P-256ánoáno
ecdsa-p384-sha384PKCS#8 / SPKI PEM, P-384ánoáno

Materiál je vždy v tvare base64:<štandardné base64> — holý reťazec sa odmietne, čo blokuje heslá. Koreň HMAC má 32–1024 náhodných bajtov (nikdy PEM, nikdy jeden opakovaný bajt); Ed25519 je 64-bajtový tajný kľúč a/alebo 32-bajtový verejný kľúč; ECDSA je base64 textu PEM s krivkou overenou voči algoritmu. Materiál sa nikdy neobjaví vo výnimkách, logoch, udalostiach, about, var_dump() ani v serializovaných dátach.

API

use RoundlyConsulting\Sentinel\Enums\Algorithm;
use RoundlyConsulting\Sentinel\Enums\KeyDestination;

Sentinel::keys()->rings();                                  // ['default', 'http']
Sentinel::keys()->all();                                    // KeyInfo of every key in every ring — never material
Sentinel::keys()->ring()->current();                        // KeyInfo of the default ring's signing key

$ring = Sentinel::keys()->ring('http');                     // KeyRingHandle; a kid of another ring is unknown here
$ring->name();                                              // 'http'
$ring->find('acme-2026-10');                                // ?KeyInfo
$ring->all();
$ring->generate(Algorithm::Ed25519, keyId: 'acme-2026-11', activatesAt: now()->addWeek());   // stored, pending until then
$ring->import('acme-2026-10', Algorithm::EcdsaP256Sha256, $pem, owner: $partner);
$ring->rotate();                                            // RotationResult: current, previous (now verify-only)
$ring->revoke('acme-2026-10', reason: 'Partner offboarded', actor: $admin);
$ring->retire('acme-2025-10');

$lines = Sentinel::keys()->ring()->generate(Algorithm::HmacSha256, destination: KeyDestination::Config)->envSnippet;
  • Predvolené hodnoty sa líšia zámerne. generate() uloží kľúč do databázového úložiska kruhu (KeyDestination::Database; kruh bez neho vyhodí KeyDriverException::readOnly), kým KeyDestination::Config vráti riadky env v envSnippet a nič neuloží. sentinel:key:generate robí opak: riadky env, pokiaľ nezadáte --database.
  • rotate() rotuje v úložisku, kde žije aktuálny podpisový kľúč; signs_until predošlého kľúča sa nastaví na aktiváciu nového, takže sa prekrývajú bez medzery. Každý ďalší kľúč kruhu, ktorý by po tomto okamihu ešte mohol podpisovať — čakajúci kľúč z predošlej rotácie s activatesAt, kľúč pridaný súbežne —, dostane pod zámkami riadkov rovnaké signs_until, takže naraz podpisuje práve jeden kľúč; KeyRotated::$previousKeyId uvedie kľúč, ktorý podpisoval v čase potvrdenia rotácie. Kruh, ktorý podpisuje kľúčom vlastného drivera, sa odmietne (KeyDriverException::customDriver): rotujte ho v jeho vlastnom úložisku — Sentinel materiál vlastného úložiska nikdy nevypíše.
  • Label je platné UTF-8 s najviac 191 znakmi (KeyDriverException::invalidLabel) a od verzie 1.2 je viazaný v obálke kľúča v databáze.
  • revoke() a retire() menia len kľúče v databáze. Kľúč z konfigurácie vyhodí KeyDriverException::notStoredInDatabase: odvoláte ho pridaním ring:kid do SENTINEL_REVOKED_KEYS a vyradíte odstránením zo zoznamu kľúčov kruhu len na overovanie — sentinel:key:revoke a sentinel:key:retire namiesto chyby vypíšu presne tento postup.
  • Dôvod odvolania je povinný (1–1 000 znakov); retire() ukončí obdobie overovania kľúča okamžite, takže pečate, ktoré ho stále používajú, hlásia RetiredKey.
use RoundlyConsulting\Sentinel\DataTransferObjects\GenerateKeyRequest;
use RoundlyConsulting\Sentinel\DataTransferObjects\RevokeKeyRequest;
use RoundlyConsulting\Sentinel\DataTransferObjects\RotateKeyRequest;

$key = Sentinel::generateKey(new GenerateKeyRequest('http', Algorithm::Ed25519, keyId: 'acme-2027-01', owner: $partner));
$rotation = Sentinel::rotateKey(new RotateKeyRequest('http'));
Sentinel::revokeKey(new RevokeKeyRequest('http', 'acme-2027-01', 'Partner offboarded', $admin));
Sentinel::retireKey('http', 'acme-2025-10');
Sentinel::listKeys('http');                                 // null = every ring
Sentinel::findKey('http', 'acme-2027-01');                  // ?KeyInfo
Sentinel::currentKey();                                     // null = keys.default_ring

Naraz len jedna rotácia kruhu

Zámky riadkov pokrývajú len existujúce kľúče, preto databázová rotácia počas celej svojej transakcie drží aj zámok kruhu na pripojení kľúčov — bez tabuľky, bez migrácie — a dve súbežné prvé rotácie prázdneho kruhu už nikdy neuložia každá svoj podpisový kľúč:

EngineZámok
PostgreSQLpg_advisory_xact_lock ako prvý príkaz transakcie po SET TRANSACTION ISOLATION LEVEL READ COMMITTED (snapshot REPEATABLE READ by vznikol ešte pred čakaním); uvoľní sa pri commite alebo rollbacku.
MySQL / MariaDBGET_LOCK('sentinel:keys:<crc32 databázy + kruhu>') pred transakciou, RELEASE_LOCK po nej; čaká najviac innodb_lock_wait_timeout, potom KeyDriverException (ringBusy).
SQLiteŽiadny — naraz zapisuje len jeden; prehraná transakcia zlyhá s „database is locked“, zopakuje sa a uvidí kľúč víťaza.

Vo vašej vlastnej transakcii drží PostgreSQL zámok až do jej konca, no úroveň izolácie už zmeniť nevie: pri REPEATABLE READ rotujte mimo nej. Zámok zoraďuje len rotácie — generate() a import() pridávajú kľúče bez neho. Kruh, ktorému súbeh pred verziou 1.1.1 nechal dva podpisové kľúče, netreba opravovať: podpisuje novší, overujú oba a ďalšia rotácia oba zosadí. Ukáže to sentinel:key:list — viac než jeden aktívny kľúč bez signs_until znamená, že je čas na rotáciu.

Stavy

Stavy zodpovedajú NIST SP 800-57 — pending → active → verify-only → retired, plus revoked — a efektívny stav sa určuje v tomto poradí:

StavKedyPodpisujeOveruje
Revokedring:kid v keys.revoked (vždy vyhráva), alebo odvolanýnienie
Retireduplynulo verifies_until, alebo vyradenýnienie (dôkazy v denníku sa stále overia)
Pendingactivates_at v budúcnostinienie
VerifyOnlyuplynulo signs_until, kľúč previous v konfigurácii, alebo len verejný materiálnieáno
Activeinakáno (so súkromným/tajným materiálom)áno

SENTINEL_REVOKED_KEYS (ring:kid,…) odvolá kľúč bez ohľadu na to, čo hovorí jeho driver — obnovený riadok v databáze odvolanie nikdy nezruší.

Import kľúčov

Pridajte partnera za behu — bez nasadenia, s kľúčom viazaným na model partnera:

$ring->import('acme-2026-10', Algorithm::Ed25519, $publicKeyPemOrBase64, owner: $partner, label: 'ACME');
Sentinel::importKey(new ImportKeyRequest('http', 'acme-2026-10', Algorithm::Ed25519, $pem, owner: $partner));
php artisan sentinel:key:import acme-2026-10 --ring=http --algorithm=ed25519 --file=acme.pem \
    --owner-type=partner --owner-id=7

import() uloží existujúci materiál do databázového úložiska kruhu: verejný kľúč partnera (Ed25519 surový alebo SPKI PEM, ECDSA PEM) alebo dohodnuté tajomstvo HMAC, viazaný na model vlastníka a len na overovanie — nikdy nepodpisuje, ani ako aktuálny kľúč kruhu, ani cez Http::withSignature() — pokiaľ nie je importovaný so signing: true (vlastný pár kľúčov presúvaný z prostredia do databázy; bez toho sa súkromný materiál odmietne). Stav, vlastník aj label sú viazané v zašifrovanej obálke — prepnutie stĺpca status alebo prepísanie labelu v SQL spôsobí, že riadok neprejde kontrolou integrity —, kid musí byť voľný v celom kruhu a odošle sa KeyImported.

Rotácia

  • Databázový kruh: rotate() vytvorí nový aktívny kľúč (voliteľne s neskoršou aktiváciou cez activatesAt); predošlý kľúč je po aktivácii nového len na overovanie. Staré pečate ostanú Intact, nové používajú nový kid.
  • Kruh z konfigurácie: sentinel:key:rotate vypíše nové SENTINEL_KEY_ID / SENTINEL_KEY a aktualizované SENTINEL_PREVIOUS_KEYS so starým kľúčom len na overovanie. Pri rotácii z kľúča Ed25519 či ECDSA na HMAC vypíše aj prázdne SENTINEL_PUBLIC_KEY= — použite ho, inak starý verejný kľúč ostane a kruh sa odmietne načítať (kľúč HMAC nemá verejnú polovicu).
  • Potom presuňte staré pečate na nový kľúč cez sentinel:reseal — nikdy potichu nelegalizuje zmenu a --from-key= ho obmedzí na jeden starý kid.
  • Kľúč vyraďte, keď ho už nič nepoužíva (sentinel:key:retire odmietne, kým ho pečate na ktoromkoľvek pripojení denníka používajú, pokiaľ nezadáte --force); kompromitovaný kľúč okamžite odvolajte.

Drivery config a database

Driver config drží kľúče mimo databázy — odporúčané nastavenie pre pečate:

SENTINEL_KEY_ID=default-20261002-k3f9qa
SENTINEL_ALGORITHM=hmac-sha256
SENTINEL_KEY="base64:…"
SENTINEL_PUBLIC_KEY="base64:…"                  # asymmetric keys; alone = verify-only
SENTINEL_PREVIOUS_KEYS="default-20250901-a1b2c3|hmac-sha256|base64:…"   # verify-only, comma-separated

Driver database ukladá riadky sentinel_keys, ktorých autoritatívna obálka je AES-256-GCM pod kľúčom odvodeným z APP_KEY len pre obálky (s podporou rotácie cez APP_PREVIOUS_KEYS). Viaže každé pole — kruh, kid, algoritmus, materiál, verejný kľúč, stav, dátumy, vlastníka a od verzie 1.2 aj label — takže riadok, ktorého nešifrované stĺpce nesúhlasia s obálkou, vyvolá KeyIntegrityException, spustí KeyIntegrityViolated a považuje sa za neznámy. Kľúče v databáze sú len také bezpečné ako APP_KEY, preto pre predvolený kruh uprednostnite driver config. chain číta svoje drivery v poradí — vyhráva prvý podpisový kľúč a odvolaný podpisový kľúč jedného úložiska (aj vlastného) sa preskočí v prospech ďalšieho.

Viazanie labelu (prechod z 1.1)

Od verzie 1.2 je label kľúča v databáze viazaný v jeho obálke (sentinel.key/2) rovnako ako vlastník: prepísanie, vymazanie či pridanie labelu v SQL spôsobí, že kľúč neprejde kontrolou integrity. Sentinel 1.1 držal label len v obyčajnom stĺpci (sentinel.key/1). Po composer update tieto kľúče fungujú ďalej — nič sa nemigruje —, no ich labely ostanú neviazané, kým sa znovu nezapečatia:

NastavenieKľúč sentinel.key/1…
require_bound_label vypnuté (predvolené)sa otvorí; label sa číta zo stĺpca, neviazaný
require_bound_label zapnuténeprejde kontrolou integrity (KeyIntegrityViolated, považuje sa za neznámy)

Každý zápis kľúča už prepečatí: generovanie, import, odvolanie, vyradenie aj zosadenie predošlého kľúča pri rotácii zapíšu sentinel.key/2 a naviažu label, ktorý riadok v tej chvíli drží. Zvyšok naviažete zámerne:

  • Najprv nasaďte 1.2 na všetky uzly. Uzol s 1.1 obálku sentinel.key/2 neotvorí — každý kľúč, ktorý 1.2 zapíše či prepečatí, sa tam javí ako zlyhanie integrity, a preto ani niet návratu na 1.1.x, keď už 1.2 kľúče zapísala. Kým bežia uzly 1.1 a 1.2 vedľa seba, kľúče nezapisujte (generate, import, rotate, revoke, retire).
  • Prepečaťte, na uzle, ktorý môže zapisovať do sentinel_keys. Každý kľúč sa pred zápisom vypíše ako ring:kid → "label" — skontrolujte každý label: naviaže sa ten, ktorý riadok drží v čase prepečatenia, takže dôvera začína tu. Kľúč, ktorý neprejde kontrolou integrity, sa vypíše, nikdy sa neprepečatí a príkaz skončí kódom 1: skôr než budete pokračovať, zistite prečo. Opakované spustenie už nič neprepečatí (already bound).
  • Zapnite striktný režim: SENTINEL_REQUIRE_BOUND_LABEL=true. Odvtedy sa obálka sentinel.key/1 — obnovená zo starej zálohy, s prepísaným labelom — už neotvorí.
php artisan sentinel:key:reseal --dry-run     # what would be bound, nothing written
php artisan sentinel:key:reseal               # every ring that stores keys in the database
php artisan sentinel:key:reseal --ring=logs   # or one ring

sentinel:check to hlási ako key_envelopes: varovanie, kým ostávajú kľúče sentinel.key/1 (počty po kruhoch, nikdy kid ani label — takže sentinel:check --strict skončí kódom 1, kým kľúče neprepečatíte), zlyhanie, ak je require_bound_label zapnuté a také kľúče ešte existujú, a ok, keď je každý kľúč viazaný. Nová inštalácia 1.2 kľúče sentinel.key/1 nemá a require_bound_label môže zapnúť hneď. Prepečatenie každý riadok znovu načíta pod jeho zámkom, takže súbežné odvolanie nikdy nezruší, a obálku zašifruje pod aktuálnym APP_KEY.

Uzly len na overovanie

Servery, ktoré majú overovať, no nikdy nepečatiť, nakonfigurujú len verejnú polovicu (SENTINEL_KEY_ID, SENTINEL_ALGORITHM, SENTINEL_PUBLIC_KEY, bez SENTINEL_KEY) a SENTINEL_AUTO_SEAL=false. Overia všetko; explicitné seal() vyhodí NoSigningKeyException.

Prejavte lásku k open source

Tento balík je zadarmo pod licenciou MIT. Ak vám šetrí čas, jednorazový príspevok alebo členstvo na Patreone nám pomôže ho ďalej udržiavať, testovať a dokumentovať.

Ďalšie spôsoby podpory vrátane kryptomien

Odoslaním daru súhlasíte s našimi podmienkami prijímania darov.

Chcete to zabudovať do svojho produktu?

Naše balíky integrujeme do zákazkových Laravel a AI riešení. Napíšte nám, na čom pracujete, a ozveme sa do 48 hodín.