Some checks failed
🚀 Build & Deploy Image / Determine Build Necessity (push) Failing after 10m14s
🚀 Build & Deploy Image / Build Runtime Base Image (push) Has been skipped
🚀 Build & Deploy Image / Build Docker Image (push) Has been skipped
🚀 Build & Deploy Image / Run Tests & Quality Checks (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Staging (push) Has been skipped
🚀 Build & Deploy Image / Auto-deploy to Production (push) Has been skipped
Security Vulnerability Scan / Check for Dependency Changes (push) Failing after 11m25s
Security Vulnerability Scan / Composer Security Audit (push) Has been cancelled
- Remove middleware reference from Gitea Traefik labels (caused routing issues) - Optimize Gitea connection pool settings (MAX_IDLE_CONNS=30, authentication_timeout=180s) - Add explicit service reference in Traefik labels - Fix intermittent 504 timeouts by improving PostgreSQL connection handling Fixes Gitea unreachability via git.michaelschiemer.de
7.8 KiB
7.8 KiB
Route Authorization System
Dokumentation des namespace-basierten Route Authorization Systems.
Übersicht
Das Route Authorization System ermöglicht die Zugangskontrolle für Routes auf Basis von:
- Legacy
#[Auth]Attribute - Backward compatibility - Namespace-basierte Blockierung - Blockiere ganze Controller-Namespaces (z.B.
App\Application\Admin\*) - Namespace-basierte IP-Restrictions - IP-basierte Zugriffskontrolle per Namespace
- Route-spezifische
#[IpAuth]Attribute - Feinkörnige IP-Kontrolle per Route
Architektur
RouteAuthorizationService
├── checkLegacyAuthAttribute()
├── checkNamespaceAccessPolicy() [NEU]
├── checkNamespaceIpRestrictions()
└── checkRouteIpAuthAttribute()
Der Service wird in der RoutingMiddleware nach dem Routing aufgerufen, sodass die gematchte Route bekannt ist.
Konfiguration
Initializer-basierte Konfiguration
Location: src/Framework/Auth/RouteAuthorizationServiceInitializer.php
#[Initializer]
public function __invoke(Container $container): RouteAuthorizationService
{
$namespaceConfig = [
// Namespace Pattern => Configuration
'App\Application\Admin\*' => [
'visibility' => 'admin', // IP-based restriction
'access_policy' => NamespaceAccessPolicy::blocked()
],
];
return new RouteAuthorizationService(
config: $this->config,
namespaceConfig: $namespaceConfig
);
}
Namespace Patterns
Wildcard-basierte Patterns:
App\Application\Admin\*- Matched alle Admin-ControllerApp\Application\Api\*- Matched alle API-ControllerApp\Application\*- Matched alle Application-Controller
Exact Match:
App\Application\Admin\Dashboard- Nur exakt dieser Namespace
Use Cases
1. Admin-Bereich komplett blockieren
$namespaceConfig = [
'App\Application\Admin\*' => [
'access_policy' => NamespaceAccessPolicy::blocked()
],
];
Ergebnis: Alle Admin-Controller werfen RouteNotFound (404)
2. Admin-Bereich mit Allowlist
use App\Application\Admin\LoginController;
use App\Application\Admin\HealthCheckController;
$namespaceConfig = [
'App\Application\Admin\*' => [
'access_policy' => NamespaceAccessPolicy::blockedExcept(
LoginController::class,
HealthCheckController::class
)
],
];
Ergebnis:
- ✅
LoginControllerundHealthCheckControlleröffentlich erreichbar - ❌ Alle anderen Admin-Controller blockiert
3. Kombination: IP-Restriction + Namespace-Blocking
$namespaceConfig = [
'App\Application\Admin\*' => [
'visibility' => 'admin', // Nur Admin-IPs erlaubt
'access_policy' => NamespaceAccessPolicy::blockedExcept(
LoginController::class // Aber Login ist public
)
],
];
Ergebnis:
- ✅
LoginController- öffentlich erreichbar (trotz admin visibility) - 🔒 Alle anderen Admin-Controller - nur von Admin-IPs
4. API-Bereich mit IP-Restriction (ohne Blocking)
$namespaceConfig = [
'App\Application\Api\*' => [
'visibility' => 'local', // Nur localhost/private IPs
// Kein access_policy - keine Namespace-Blockierung
],
];
Ergebnis: API nur von localhost/private IPs erreichbar
5. Mehrere Namespace-Policies
$namespaceConfig = [
// Admin komplett gesperrt
'App\Application\Admin\*' => [
'access_policy' => NamespaceAccessPolicy::blocked()
],
// API nur von localhost
'App\Application\Api\*' => [
'visibility' => 'local'
],
// Internal Tools nur admin IPs
'App\Application\Internal\*' => [
'visibility' => 'admin',
'access_policy' => NamespaceAccessPolicy::blocked()
],
];
Value Objects
NamespaceAccessPolicy
// Alle Controller im Namespace blockieren
NamespaceAccessPolicy::blocked()
// Alle blockieren außer spezifische Controller
NamespaceAccessPolicy::blockedExcept(
LoginController::class,
HealthCheckController::class
)
// Alle erlauben (default)
NamespaceAccessPolicy::allowed()
// Prüfen ob Controller blockiert ist
$policy->isControllerBlocked(Dashboard::class) // true/false
Visibility Modes (IP-Restrictions)
Predefined Modes:
public- Keine IP-Restrictionsadmin- Nur Admin-IPs (WireGuard, etc.)local- Nur localhost/127.0.0.1development- Development-IPsprivate- Alias fürlocalcustom- Custom IP-Liste via Config
Execution Order
- Legacy Auth Attribute - Backward compatibility check
- Namespace Access Policy - Block/Allow basierend auf Controller-Klasse
- Namespace IP Restrictions - IP-basierte Zugriffskontrolle
- Route IP Auth Attribute - Feinkörnige Route-Level IP-Kontrolle
Alle Checks werfen RouteNotFound bei Failure (versteckt Route-Existenz).
Testing
Unit Test Beispiel
use App\Framework\Auth\RouteAuthorizationService;
use App\Framework\Auth\ValueObjects\NamespaceAccessPolicy;
it('blocks admin controllers except allowlist', function () {
$config = ['App\Application\Admin\*' => [
'access_policy' => NamespaceAccessPolicy::blockedExcept(
LoginController::class
)
]];
$service = new RouteAuthorizationService(
config: $this->config,
namespaceConfig: $config
);
// Should throw RouteNotFound for Dashboard
expect(fn() => $service->authorize($request, $dashboardRoute))
->toThrow(RouteNotFound::class);
// Should allow LoginController
expect(fn() => $service->authorize($request, $loginRoute))
->not->toThrow(RouteNotFound::class);
});
Best Practices
1. Namespace-Blocking für Production
- Blockiere Admin/Internal-Bereiche in Production
- Nutze Allowlist nur für wirklich öffentliche Endpoints (Login, Health)
2. IP-Restrictions für Sensitive Bereiche
- Kombiniere Namespace-Blocking mit IP-Restrictions
- Nutze
visibility: 'admin'für maximale Sicherheit
3. Graceful Error Handling
- Alle Checks werfen
RouteNotFound(404) - Versteckt Route-Existenz vor Angreifern
- Keine Information Leakage
4. Konfiguration via Initializer
- Zentrale Konfiguration in
RouteAuthorizationServiceInitializer - Environment-spezifische Configs möglich (dev vs. production)
- Type-safe via Value Objects
Migration Guide
Von altem System (RoutingMiddleware::withNamespaceConfig)
Alt:
RoutingMiddleware::withNamespaceConfig(
$router, $dispatcher, $config, $performance, $container,
namespaceConfig: [
'App\Application\Admin\*' => ['visibility' => 'admin']
]
);
Neu:
// In RouteAuthorizationServiceInitializer
$namespaceConfig = [
'App\Application\Admin\*' => [
'visibility' => 'admin',
'access_policy' => NamespaceAccessPolicy::blocked() // NEU
]
];
Troubleshooting
Problem: Route wirft 404 obwohl sie erreichbar sein sollte
Debugging:
- Prüfe
RouteAuthorizationServiceInitializerConfig - Check ob Controller-Namespace in
namespaceConfigmatched - Prüfe
access_policy- ist Controller in Allowlist? - Check IP-Restrictions (
visibility)
Problem: Allowlist funktioniert nicht
Ursache: Controller-Klasse exakt mit ::class angeben
// ❌ Falsch
NamespaceAccessPolicy::blockedExcept('LoginController')
// ✅ Korrekt
NamespaceAccessPolicy::blockedExcept(
\App\Application\Admin\LoginController::class
)
Framework Integration
- Automatic Discovery: Service wird via
#[Initializer]automatisch registriert - DI Container: Alle Dependencies werden automatisch injected
- Type Safety: Value Objects für alle Policies
- Readonly Classes: Unveränderliche Policies für Thread-Safety
- Framework-konform: Nutzt bestehende Patterns (IpAuthPolicy, RouteNotFound)