|
18 | 18 | use App\Http\Middleware\ControlPlaneVersionResolver; |
19 | 19 | use App\Http\Middleware\NamespaceResolver; |
20 | 20 | use App\Http\Middleware\RequireRole; |
| 21 | +use App\Http\Middleware\RequireTopologyRoles; |
21 | 22 | use App\Http\Middleware\WorkerProtocolVersionResolver; |
22 | 23 | use Illuminate\Support\Facades\Route; |
23 | 24 |
|
|
46 | 47 | // omitted — it is the version-advertising endpoint and must remain callable |
47 | 48 | // without the header. |
48 | 49 | // |
| 50 | +// RequireTopologyRoles sits after protocol validation and before |
| 51 | +// NamespaceResolver on hosted routes so wrong-node requests fail closed with a |
| 52 | +// machine-readable topology reason without leaking namespace existence. |
| 53 | +// |
49 | 54 | // WorkerProtocolVersionResolver follows the same ordering for worker-plane |
50 | 55 | // routes, keeping protocol skew and namespace errors in the worker envelope. |
51 | 56 | Route::middleware([Authenticate::class])->group(function () { |
|
55 | 60 | $authenticated = RequireRole::class.':worker,operator,admin'; |
56 | 61 | $ns = NamespaceResolver::class; |
57 | 62 | $cpv = ControlPlaneVersionResolver::class; |
| 63 | + $httpControl = RequireTopologyRoles::class.':api_ingress,control_plane'; |
| 64 | + $httpWorker = RequireTopologyRoles::class.':api_ingress,control_plane'; |
58 | 65 | $wpv = WorkerProtocolVersionResolver::class; |
59 | 66 |
|
60 | 67 | // ── System ─────────────────────────────────────────────────────── |
61 | 68 | Route::get('/cluster/info', [HealthController::class, 'clusterInfo'])->middleware([$authenticated, $ns]); |
62 | 69 |
|
63 | 70 | // ── Namespaces ─────────────────────────────────────────────────── |
64 | | - Route::prefix('namespaces')->group(function () use ($admin, $operator, $ns, $cpv) { |
65 | | - Route::get('/', [NamespaceController::class, 'index'])->middleware([$operator, $cpv, $ns]); |
66 | | - Route::post('/', [NamespaceController::class, 'store'])->middleware([$admin, $cpv, $ns]); |
67 | | - Route::get('/{namespace}', [NamespaceController::class, 'show'])->middleware([$operator, $cpv, $ns]); |
68 | | - Route::put('/{namespace}', [NamespaceController::class, 'update'])->middleware([$admin, $cpv, $ns]); |
69 | | - Route::put('/{namespace}/external-storage', [NamespaceController::class, 'updateExternalStorage'])->middleware([$admin, $cpv, $ns]); |
| 71 | + Route::prefix('namespaces')->group(function () use ($admin, $operator, $ns, $cpv, $httpControl) { |
| 72 | + Route::get('/', [NamespaceController::class, 'index'])->middleware([$operator, $cpv, $httpControl, $ns]); |
| 73 | + Route::post('/', [NamespaceController::class, 'store'])->middleware([$admin, $cpv, $httpControl, $ns]); |
| 74 | + Route::get('/{namespace}', [NamespaceController::class, 'show'])->middleware([$operator, $cpv, $httpControl, $ns]); |
| 75 | + Route::put('/{namespace}', [NamespaceController::class, 'update'])->middleware([$admin, $cpv, $httpControl, $ns]); |
| 76 | + Route::put('/{namespace}/external-storage', [NamespaceController::class, 'updateExternalStorage'])->middleware([$admin, $cpv, $httpControl, $ns]); |
70 | 77 | }); |
71 | 78 |
|
72 | 79 | // ── External Payload Storage ─────────────────────────────────── |
73 | | - Route::prefix('storage')->middleware([$admin, $cpv, $ns])->group(function () { |
| 80 | + Route::prefix('storage')->middleware([$admin, $cpv, $httpControl, $ns])->group(function () { |
74 | 81 | Route::post('/test', [StorageController::class, 'test']); |
75 | 82 | }); |
76 | 83 |
|
77 | 84 | // ── Workflows ──────────────────────────────────────────────────── |
78 | | - Route::prefix('workflows')->middleware([$operator, $cpv, $ns])->group(function () { |
| 85 | + Route::prefix('workflows')->middleware([$operator, $cpv, $httpControl, $ns])->group(function () { |
79 | 86 | Route::get('/', [WorkflowController::class, 'index']); |
80 | 87 | Route::post('/', [WorkflowController::class, 'start']); |
81 | 88 | Route::get('/{workflowId}', [WorkflowController::class, 'show']); |
|
106 | 113 | }); |
107 | 114 |
|
108 | 115 | // ── Bridge Adapters ────────────────────────────────────────────── |
109 | | - Route::prefix('bridge-adapters')->middleware([$operator, $cpv, $ns])->group(function () { |
| 116 | + Route::prefix('bridge-adapters')->middleware([$operator, $cpv, $httpControl, $ns])->group(function () { |
110 | 117 | Route::post('/webhook/{adapter}', [BridgeAdapterController::class, 'webhook']); |
111 | 118 | }); |
112 | 119 |
|
113 | 120 | // ── Worker Task Polling ────────────────────────────────────────── |
114 | | - Route::prefix('worker')->middleware([$worker, $wpv, $ns])->group(function () { |
| 121 | + Route::prefix('worker')->middleware([$worker, $wpv, $httpWorker, $ns])->group(function () { |
115 | 122 | // Registration |
116 | 123 | Route::post('/register', [WorkerController::class, 'register']); |
117 | 124 | Route::post('/heartbeat', [WorkerController::class, 'heartbeat']); |
|
136 | 143 | }); |
137 | 144 |
|
138 | 145 | // ── Workers (Management) ────────────────────────────────────────── |
139 | | - Route::prefix('workers')->group(function () use ($admin, $operator, $ns, $cpv) { |
140 | | - Route::get('/', [WorkerManagementController::class, 'index'])->middleware([$operator, $cpv, $ns]); |
141 | | - Route::get('/{workerId}', [WorkerManagementController::class, 'show'])->middleware([$operator, $cpv, $ns]); |
142 | | - Route::delete('/{workerId}', [WorkerManagementController::class, 'destroy'])->middleware([$admin, $cpv, $ns]); |
| 146 | + Route::prefix('workers')->group(function () use ($admin, $operator, $ns, $cpv, $httpControl) { |
| 147 | + Route::get('/', [WorkerManagementController::class, 'index'])->middleware([$operator, $cpv, $httpControl, $ns]); |
| 148 | + Route::get('/{workerId}', [WorkerManagementController::class, 'show'])->middleware([$operator, $cpv, $httpControl, $ns]); |
| 149 | + Route::delete('/{workerId}', [WorkerManagementController::class, 'destroy'])->middleware([$admin, $cpv, $httpControl, $ns]); |
143 | 150 | }); |
144 | 151 |
|
145 | 152 | // ── Task Queues ────────────────────────────────────────────────── |
146 | | - Route::prefix('task-queues')->middleware([$operator, $cpv, $ns])->group(function () { |
| 153 | + Route::prefix('task-queues')->middleware([$operator, $cpv, $httpControl, $ns])->group(function () { |
147 | 154 | Route::get('/', [TaskQueueController::class, 'index']); |
148 | 155 | Route::get('/{taskQueue}/build-ids', [TaskQueueController::class, 'buildIds']); |
149 | 156 | Route::post('/{taskQueue}/build-ids/drain', [TaskQueueController::class, 'drainBuildId']); |
|
152 | 159 | }); |
153 | 160 |
|
154 | 161 | // ── Schedules ──────────────────────────────────────────────────── |
155 | | - Route::prefix('schedules')->middleware([$operator, $cpv, $ns])->group(function () { |
| 162 | + Route::prefix('schedules')->middleware([$operator, $cpv, $httpControl, $ns])->group(function () { |
156 | 163 | Route::get('/', [ScheduleController::class, 'index']); |
157 | 164 | Route::post('/', [ScheduleController::class, 'store']); |
158 | 165 | Route::get('/{scheduleId}', [ScheduleController::class, 'show']); |
|
166 | 173 | }); |
167 | 174 |
|
168 | 175 | // ── Search Attributes ──────────────────────────────────────────── |
169 | | - Route::prefix('search-attributes')->middleware([$operator, $cpv, $ns])->group(function () { |
| 176 | + Route::prefix('search-attributes')->middleware([$operator, $cpv, $httpControl, $ns])->group(function () { |
170 | 177 | Route::get('/', [SearchAttributeController::class, 'index']); |
171 | 178 | Route::post('/', [SearchAttributeController::class, 'store']); |
172 | 179 | Route::delete('/{name}', [SearchAttributeController::class, 'destroy']); |
173 | 180 | }); |
174 | 181 |
|
175 | 182 | // ── Service Catalog ────────────────────────────────────────────── |
176 | | - Route::prefix('service-endpoints')->middleware([$admin, $cpv, $ns])->group(function () { |
| 183 | + Route::prefix('service-endpoints')->middleware([$admin, $cpv, $httpControl, $ns])->group(function () { |
177 | 184 | Route::get('/', [ServiceCatalogController::class, 'endpointIndex']); |
178 | 185 | Route::post('/', [ServiceCatalogController::class, 'endpointStore']); |
179 | 186 | Route::get('/{endpointName}', [ServiceCatalogController::class, 'endpointShow']); |
|
194 | 201 | }); |
195 | 202 |
|
196 | 203 | // ── System / Operations ───────────────────────────────────────── |
197 | | - Route::prefix('system')->middleware([$admin, $cpv, $ns])->group(function () { |
| 204 | + Route::prefix('system')->middleware([$admin, $cpv, $httpControl, $ns])->group(function () { |
198 | 205 | Route::get('/health', [SystemController::class, 'health']); |
199 | 206 | Route::match(['get', 'post'], '/metrics', [SystemController::class, 'metrics']); |
200 | 207 | Route::get('/operator-metrics', [SystemController::class, 'operatorMetrics']); |
|
0 commit comments