diff --git a/src/components/kubernetes-operator/OperatorPermissionsTable.tsx b/src/components/kubernetes-operator/OperatorPermissionsTable.tsx
new file mode 100644
index 00000000..bd8b9d9d
--- /dev/null
+++ b/src/components/kubernetes-operator/OperatorPermissionsTable.tsx
@@ -0,0 +1,198 @@
+import React from 'react';
+import {
+ Table,
+ TableHeader,
+ TableBody,
+ TableRow,
+ TableHead,
+ TableCell,
+} from '@/components/ui/table';
+import {
+ useReactTable,
+ getCoreRowModel,
+ flexRender,
+} from '@tanstack/react-table';
+import type { ColumnDef } from '@tanstack/react-table';
+import permissionsData from '@/data/kubernetes/operator-permissions.json';
+
+type PermissionRow = {
+ kind?: string;
+ name?: string;
+ apiGroup?: string;
+ resources?: string[];
+ verbs: string[];
+ nonResourceUrls?: string[];
+};
+
+const rows = permissionsData as PermissionRow[];
+
+const headerCellStyle: React.CSSProperties = {
+ textAlign: 'left',
+ border: '1px solid #999CAD',
+ background: '#AFB2C2',
+ color: 'var(--sl-color-gray-1)',
+ fontFamily: 'AeonikFono',
+ fontSize: '14px',
+ fontWeight: '500',
+ lineHeight: '16px',
+ letterSpacing: '-0.15px',
+ padding: '12px 8px',
+};
+
+const bodyCellStyle: React.CSSProperties = {
+ verticalAlign: 'top',
+ textAlign: 'left',
+ border: '1px solid #999CAD',
+ color: 'var(--sl-color-gray-1)',
+ fontFamily: 'AeonikFono',
+ fontSize: '14px',
+ fontWeight: '400',
+ lineHeight: '16px',
+ letterSpacing: '-0.15px',
+ padding: '12px 8px',
+ whiteSpace: 'normal',
+};
+
+const renderCodeList = (values?: string[]) => {
+ if (!values?.length) return null;
+ return (
+ <>
+ {values.map((value, index) => (
+
+ {value}
+ {index < values.length - 1 ? ', ' : ''}
+
+ ))}
+ >
+ );
+};
+
+const columns: ColumnDef[] = [
+ {
+ accessorKey: 'kind',
+ header: () => 'Kind',
+ cell: ({ row }) => (row.original.kind ? {row.original.kind} : null),
+ },
+ {
+ accessorKey: 'name',
+ header: () => 'Name',
+ cell: ({ row }) => (row.original.name ? {row.original.name} : null),
+ },
+ {
+ accessorKey: 'apiGroup',
+ header: () => 'API Groups',
+ cell: ({ row }) => (row.original.apiGroup ? {row.original.apiGroup} : null),
+ },
+ {
+ id: 'resources',
+ header: () => 'Resources',
+ cell: ({ row }) =>
+ row.original.nonResourceUrls?.length ? (
+ <> (nonResourceURLs: {renderCodeList(row.original.nonResourceUrls)})>
+ ) : (
+ renderCodeList(row.original.resources)
+ ),
+ },
+ {
+ id: 'verbs',
+ header: () => 'Verbs',
+ cell: ({ row }) => row.original.verbs.join(', '),
+ },
+];
+
+export default function OperatorPermissionsTable() {
+ const table = useReactTable({
+ data: rows,
+ columns,
+ getCoreRowModel: getCoreRowModel(),
+ debugTable: false,
+ });
+
+ const getColumnWidth = (columnId: string) => {
+ switch (columnId) {
+ case 'kind':
+ return '14%';
+ case 'name':
+ return '24%';
+ case 'apiGroup':
+ return '16%';
+ case 'resources':
+ return '23%';
+ case 'verbs':
+ return '23%';
+ default:
+ return 'auto';
+ }
+ };
+
+ const getMinWidth = (columnId: string) => {
+ switch (columnId) {
+ case 'kind':
+ return '110px';
+ case 'name':
+ return '220px';
+ case 'apiGroup':
+ return '170px';
+ case 'resources':
+ return '260px';
+ case 'verbs':
+ return '300px';
+ default:
+ return '80px';
+ }
+ };
+
+ return (
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+
+ {headerGroup.headers.map((header) => (
+
+ {flexRender(header.column.columnDef.header, header.getContext())}
+
+ ))}
+
+ ))}
+
+
+ {table.getRowModel().rows.map((row) => (
+
+ {row.getVisibleCells().map((cell) => (
+
+ {flexRender(cell.column.columnDef.cell, cell.getContext())}
+
+ ))}
+
+ ))}
+
+
+
+ );
+}
diff --git a/src/content/docs/aws/enterprise/kubernetes/kubernetes-operator.md b/src/content/docs/aws/enterprise/kubernetes/kubernetes-operator.mdx
similarity index 80%
rename from src/content/docs/aws/enterprise/kubernetes/kubernetes-operator.md
rename to src/content/docs/aws/enterprise/kubernetes/kubernetes-operator.mdx
index b0e47fe3..73cd361e 100644
--- a/src/content/docs/aws/enterprise/kubernetes/kubernetes-operator.md
+++ b/src/content/docs/aws/enterprise/kubernetes/kubernetes-operator.mdx
@@ -7,6 +7,8 @@ sidebar:
tags: ["Enterprise"]
---
+import OperatorPermissionsTable from '../../../../../components/kubernetes-operator/OperatorPermissionsTable';
+
The LocalStack Operator is our Kubernetes-native way to deploy and manage LocalStack instances. It abstracts Kubernetes-specific configuration and automates operational tasks, making LocalStack deployments more consistent and easier to maintain. It can manage multiple LocalStack instances within a cluster to provide isolated local clouds for multiple users.
The Operator manages the full lifecycle of LocalStack resources and enables advanced Kubernetes integrations that are difficult to configure manually.
@@ -136,27 +138,7 @@ CRD documentation is currently maintained manually. For a full reference of avai
The Operator manifest creates all required `Roles`, `ClusterRoles`, and `bindings`.
-
-| **Kind** | **Name** | **API Groups** | **Resources** | **Verbs** |
-| --- | --- | --- | --- | --- |
-| **Role** | `localstack-operator-leader-election-role` | | `configmaps` | get, list, watch, create, update, patch, delete |
-| | | `coordination.k8s.io` | `leases` | get, list, watch, create, update, patch, delete |
-| | | | `events` | create, patch |
-| **ClusterRole** | `localstack-operator-manager-role` | | `configmaps` | delete, get, list, patch, update, watch |
-| | | | `events` | create, patch |
-| | | | `pods`, `pods/exec`, `pods/log` | create, delete, deletecollection, get, list, patch, update, watch |
-| | | | `secrets` | get, list, watch |
-| | | | `serviceaccounts` | create, delete, get, list, update, watch |
-| | | | `services` | create, delete, get, list, patch, update, watch |
-| | | `api.localstack.cloud` | `localstacks` | create, delete, get, list, patch, update, watch |
-| | | `api.localstack.cloud` | `localstacks/finalizers` | update |
-| | | `api.localstack.cloud` | `localstacks/status` | get, patch, update |
-| | | `apps` | `deployments` | create, delete, get, list, patch, update, watch |
-| | | `apps` | `deployments/scale` | create, delete, get, list, patch, update, watch |
-| | | `rbac.authorization.k8s.io` | `rolebindings`, `roles` | create, delete, list, update, watch |
-| **ClusterRole** | `localstack-operator-metrics-reader` | | (nonResourceURLs: `/metrics`) | get |
-| **ClusterRole** | `localstack-operator-proxy-role` | `authentication.k8s.io` | `tokenreviews` | create |
-| | | `authorization.k8s.io` | `subjectaccessreviews` | create |
+
### Manager ClusterRole
@@ -261,4 +243,4 @@ Ensure that:
* Each LocalStack instance requires its **own auth token**
* The Operator supports **LocalStack Pro images only**
* DNS integration and Cloud Pod automation are Operator-exclusive features
-:::
\ No newline at end of file
+:::
diff --git a/src/data/kubernetes/operator-permissions.json b/src/data/kubernetes/operator-permissions.json
new file mode 100644
index 00000000..7a7cb78a
--- /dev/null
+++ b/src/data/kubernetes/operator-permissions.json
@@ -0,0 +1,100 @@
+[
+ {
+ "kind": "Role",
+ "name": "localstack-operator-leader-election-role",
+ "resources": ["configmaps"],
+ "verbs": ["get", "list", "watch", "create", "update", "patch", "delete"]
+ },
+ {
+ "apiGroup": "coordination.k8s.io",
+ "resources": ["leases"],
+ "verbs": ["get", "list", "watch", "create", "update", "patch", "delete"]
+ },
+ {
+ "resources": ["events"],
+ "verbs": ["create", "patch"]
+ },
+ {
+ "kind": "ClusterRole",
+ "name": "localstack-operator-manager-role",
+ "resources": ["configmaps"],
+ "verbs": ["delete", "get", "list", "patch", "update", "watch"]
+ },
+ {
+ "resources": ["events"],
+ "verbs": ["create", "patch"]
+ },
+ {
+ "resources": ["pods", "pods/exec", "pods/log"],
+ "verbs": [
+ "create",
+ "delete",
+ "deletecollection",
+ "get",
+ "list",
+ "patch",
+ "update",
+ "watch"
+ ]
+ },
+ {
+ "resources": ["secrets"],
+ "verbs": ["get", "list", "watch"]
+ },
+ {
+ "resources": ["serviceaccounts"],
+ "verbs": ["create", "delete", "get", "list", "update", "watch"]
+ },
+ {
+ "resources": ["services"],
+ "verbs": ["create", "delete", "get", "list", "patch", "update", "watch"]
+ },
+ {
+ "apiGroup": "api.localstack.cloud",
+ "resources": ["localstacks"],
+ "verbs": ["create", "delete", "get", "list", "patch", "update", "watch"]
+ },
+ {
+ "apiGroup": "api.localstack.cloud",
+ "resources": ["localstacks/finalizers"],
+ "verbs": ["update"]
+ },
+ {
+ "apiGroup": "api.localstack.cloud",
+ "resources": ["localstacks/status"],
+ "verbs": ["get", "patch", "update"]
+ },
+ {
+ "apiGroup": "apps",
+ "resources": ["deployments"],
+ "verbs": ["create", "delete", "get", "list", "patch", "update", "watch"]
+ },
+ {
+ "apiGroup": "apps",
+ "resources": ["deployments/scale"],
+ "verbs": ["create", "delete", "get", "list", "patch", "update", "watch"]
+ },
+ {
+ "apiGroup": "rbac.authorization.k8s.io",
+ "resources": ["rolebindings", "roles"],
+ "verbs": ["create", "delete", "list", "update", "watch"]
+ },
+ {
+ "kind": "ClusterRole",
+ "name": "localstack-operator-metrics-reader",
+ "nonResourceUrls": ["/metrics"],
+ "verbs": ["get"]
+ },
+ {
+ "kind": "ClusterRole",
+ "name": "localstack-operator-proxy-role",
+ "apiGroup": "authentication.k8s.io",
+ "resources": ["tokenreviews"],
+ "verbs": ["create"]
+ },
+ {
+ "apiGroup": "authorization.k8s.io",
+ "resources": ["subjectaccessreviews"],
+ "verbs": ["create"]
+ }
+]