feat(Production): Complete production deployment infrastructure

- Add comprehensive health check system with multiple endpoints
- Add Prometheus metrics endpoint
- Add production logging configurations (5 strategies)
- Add complete deployment documentation suite:
  * QUICKSTART.md - 30-minute deployment guide
  * DEPLOYMENT_CHECKLIST.md - Printable verification checklist
  * DEPLOYMENT_WORKFLOW.md - Complete deployment lifecycle
  * PRODUCTION_DEPLOYMENT.md - Comprehensive technical reference
  * production-logging.md - Logging configuration guide
  * ANSIBLE_DEPLOYMENT.md - Infrastructure as Code automation
  * README.md - Navigation hub
  * DEPLOYMENT_SUMMARY.md - Executive summary
- Add deployment scripts and automation
- Add DEPLOYMENT_PLAN.md - Concrete plan for immediate deployment
- Update README with production-ready features

All production infrastructure is now complete and ready for deployment.
This commit is contained in:
2025-10-25 19:18:37 +02:00
parent caa85db796
commit fc3d7e6357
83016 changed files with 378904 additions and 20919 deletions

View File

@@ -0,0 +1,247 @@
<?php
declare(strict_types=1);
use App\Framework\Auth\RouteAuthorizationService;
use App\Framework\Auth\ValueObjects\NamespaceAccessPolicy;
use App\Framework\Config\TypedConfiguration;
use App\Framework\DI\DefaultContainer;
use App\Framework\Http\HttpRequest;
use App\Framework\Http\IpAddress;
use App\Framework\Http\Method;
use App\Framework\Router\Exception\RouteNotFound;
use App\Framework\Router\RouteContext;
describe('Route Authorization Integration', function () {
beforeEach(function () {
$this->container = new DefaultContainer();
// Setup stub config
$this->config = createStubConfig(debug: false);
});
afterEach(function () {
Mockery::close();
});
it('integrates namespace blocking with IP restrictions', function () {
// Real-world scenario: Admin area with IP restrictions AND namespace blocking
$namespaceConfig = [
'App\Application\Admin\*' => [
'visibility' => 'admin', // IP-restricted to admin IPs
'access_policy' => NamespaceAccessPolicy::blockedExcept(
'App\Application\Admin\LoginController',
'App\Application\Admin\HealthController'
),
],
];
$service = new RouteAuthorizationService($this->config, $namespaceConfig);
// Test 1: Login should be accessible from any IP (in allowlist)
$loginRequest = createHttpRequest('192.168.1.100', '/admin/login');
$loginRoute = createRouteContext('App\Application\Admin\LoginController', '/admin/login');
expect(fn () => $service->authorize($loginRequest, $loginRoute))
->not->toThrow(RouteNotFound::class);
// Test 2: Dashboard blocked (not in allowlist)
$dashboardRequest = createHttpRequest('192.168.1.100', '/admin/dashboard');
$dashboardRoute = createRouteContext('App\Application\Admin\Dashboard', '/admin/dashboard');
expect(fn () => $service->authorize($dashboardRequest, $dashboardRoute))
->toThrow(RouteNotFound::class);
// Test 3: Health controller allowed (in allowlist)
$healthRequest = createHttpRequest('10.0.0.5', '/admin/health');
$healthRoute = createRouteContext('App\Application\Admin\HealthController', '/admin/health');
expect(fn () => $service->authorize($healthRequest, $healthRoute))
->not->toThrow(RouteNotFound::class);
});
it('handles multiple namespace configurations independently', function () {
$namespaceConfig = [
// Admin: Completely blocked
'App\Application\Admin\*' => [
'access_policy' => NamespaceAccessPolicy::blocked(),
],
// API: Blocked except health endpoint
'App\Application\Api\*' => [
'access_policy' => NamespaceAccessPolicy::blockedExcept(
'App\Application\Api\HealthController'
),
],
// Web: No restrictions
'App\Application\Web\*' => [
'visibility' => 'public',
],
];
$service = new RouteAuthorizationService($this->config, $namespaceConfig);
$request = createHttpRequest('127.0.0.1', '/test');
// Admin: All blocked
$adminRoute = createRouteContext('App\Application\Admin\Dashboard', '/admin/dashboard');
expect(fn () => $service->authorize($request, $adminRoute))
->toThrow(RouteNotFound::class);
// API: Health allowed
$apiHealthRoute = createRouteContext('App\Application\Api\HealthController', '/api/health');
expect(fn () => $service->authorize($request, $apiHealthRoute))
->not->toThrow(RouteNotFound::class);
// API: Users blocked
$apiUsersRoute = createRouteContext('App\Application\Api\UsersController', '/api/users');
expect(fn () => $service->authorize($request, $apiUsersRoute))
->toThrow(RouteNotFound::class);
// Web: Allowed
$webRoute = createRouteContext('App\Application\Web\HomeController', '/');
expect(fn () => $service->authorize($request, $webRoute))
->not->toThrow(RouteNotFound::class);
});
it('production-like configuration scenario', function () {
// Realistic production setup
$namespaceConfig = [
// Internal tools - completely locked down
'App\Application\Internal\*' => [
'visibility' => 'admin',
'access_policy' => NamespaceAccessPolicy::blocked(),
],
// Admin - IP restricted, only public endpoints accessible
'App\Application\Admin\*' => [
'visibility' => 'admin',
'access_policy' => NamespaceAccessPolicy::blockedExcept(
'App\Application\Admin\LoginController'
),
],
// API - public but monitored
'App\Application\Api\*' => [
'visibility' => 'public',
// No access_policy - all API routes accessible
],
];
$service = new RouteAuthorizationService($this->config, $namespaceConfig);
// Public user accessing different areas
$publicRequest = createHttpRequest('93.184.216.34', '/test');
// Internal: Blocked (even from public IP, namespace blocked + IP restricted)
$internalRoute = createRouteContext('App\Application\Internal\ToolsController', '/internal/tools');
expect(fn () => $service->authorize($publicRequest, $internalRoute))
->toThrow(RouteNotFound::class);
// Admin Login: Allowed (in allowlist, IP restriction doesn't apply to allowlist)
$adminLoginRoute = createRouteContext('App\Application\Admin\LoginController', '/admin/login');
expect(fn () => $service->authorize($publicRequest, $adminLoginRoute))
->not->toThrow(RouteNotFound::class);
// Admin Dashboard: Blocked (not in allowlist)
$adminDashboardRoute = createRouteContext('App\Application\Admin\Dashboard', '/admin/dashboard');
expect(fn () => $service->authorize($publicRequest, $adminDashboardRoute))
->toThrow(RouteNotFound::class);
// API: Allowed (public visibility, no namespace blocking)
$apiRoute = createRouteContext('App\Application\Api\UsersController', '/api/users');
expect(fn () => $service->authorize($publicRequest, $apiRoute))
->not->toThrow(RouteNotFound::class);
});
it('handles deeply nested namespaces correctly', function () {
$namespaceConfig = [
'App\Application\Admin\*' => [
'access_policy' => NamespaceAccessPolicy::blockedExcept(
'App\Application\Admin\Auth\LoginController'
),
],
];
$service = new RouteAuthorizationService($this->config, $namespaceConfig);
$request = createHttpRequest('127.0.0.1', '/test');
// Deeply nested allowed controller
$loginRoute = createRouteContext(
'App\Application\Admin\Auth\LoginController',
'/admin/auth/login'
);
expect(fn () => $service->authorize($request, $loginRoute))
->not->toThrow(RouteNotFound::class);
// Deeply nested blocked controller
$userRoute = createRouteContext(
'App\Application\Admin\Users\Management\UserController',
'/admin/users/management'
);
expect(fn () => $service->authorize($request, $userRoute))
->toThrow(RouteNotFound::class);
});
it('handles no namespace configuration gracefully', function () {
// Service with empty config should allow everything
$service = new RouteAuthorizationService($this->config, []);
$request = createHttpRequest('127.0.0.1', '/test');
$routes = [
createRouteContext('App\Application\Admin\Dashboard', '/admin/dashboard'),
createRouteContext('App\Application\Api\UsersController', '/api/users'),
createRouteContext('App\Application\Web\HomeController', '/'),
];
foreach ($routes as $route) {
expect(fn () => $service->authorize($request, $route))
->not->toThrow(RouteNotFound::class);
}
});
});
// Helper functions
function createStubConfig(bool $debug = false): TypedConfiguration
{
return new class ($debug) extends TypedConfiguration {
public function __construct(bool $debug)
{
$this->app = (object)['debug' => $debug];
}
};
}
function createHttpRequest(string $ipAddress, string $path): HttpRequest
{
$server = Mockery::mock('server');
$server->shouldReceive('getClientIp')
->andReturn(IpAddress::fromString($ipAddress));
$request = Mockery::mock(HttpRequest::class);
$request->server = $server;
$request->path = $path;
$request->method = Method::GET;
return $request;
}
function createRouteContext(string $controllerClass, string $path): RouteContext
{
$route = (object)[
'controller' => $controllerClass,
'action' => 'index',
'attributes' => [],
];
$match = (object)['route' => $route];
$routeContext = Mockery::mock(RouteContext::class);
$routeContext->match = $match;
$routeContext->path = $path;
$routeContext->shouldReceive('isSuccess')->andReturn(true);
return $routeContext;
}