raijin-server 0.3.6__py3-none-any.whl → 0.3.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
raijin_server/__init__.py CHANGED
@@ -1,5 +1,5 @@
1
1
  """Pacote principal do CLI Raijin Server."""
2
2
 
3
- __version__ = "0.3.6"
3
+ __version__ = "0.3.7"
4
4
 
5
5
  __all__ = ["__version__"]
raijin_server/cli.py CHANGED
@@ -17,7 +17,6 @@ from rich.table import Table
17
17
 
18
18
  from raijin_server import __version__
19
19
  from raijin_server.modules import (
20
- apokolips_demo,
21
20
  bootstrap,
22
21
  calico,
23
22
  cert_manager,
@@ -98,7 +97,7 @@ MODULES: Dict[str, Callable[[ExecutionContext], None]] = {
98
97
  "minio": minio.run,
99
98
  "prometheus": prometheus.run,
100
99
  "grafana": grafana.run,
101
- "apokolips_demo": apokolips_demo.run,
100
+
102
101
  "secrets": secrets.run,
103
102
  "loki": loki.run,
104
103
  "harbor": harbor.run,
@@ -134,7 +133,7 @@ MODULE_DESCRIPTIONS: Dict[str, str] = {
134
133
  "minio": "Objeto storage S3-compat via Helm",
135
134
  "prometheus": "Stack kube-prometheus",
136
135
  "grafana": "Dashboards e datasource Prometheus",
137
- "apokolips_demo": "Landing page Apokolips para testar ingress externo",
136
+
138
137
  "secrets": "Secrets management (Vault + External Secrets Operator)",
139
138
  "loki": "Logs centralizados Loki",
140
139
  "harbor": "Container registry privado com vulnerability scanning",
@@ -552,11 +551,6 @@ def grafana(ctx: typer.Context) -> None:
552
551
  _run_module(ctx, "grafana")
553
552
 
554
553
 
555
- @app.command(name="apokolips-demo")
556
- def apokolips_demo_cmd(ctx: typer.Context) -> None:
557
- _run_module(ctx, "apokolips_demo")
558
-
559
-
560
554
  @app.command()
561
555
  def loki(ctx: typer.Context) -> None:
562
556
  _run_module(ctx, "loki")
@@ -359,60 +359,6 @@ def verify_secrets(ctx: ExecutionContext) -> bool:
359
359
  return sealed_ok and eso_ok
360
360
 
361
361
 
362
- def verify_apokolips_demo(ctx: ExecutionContext) -> bool:
363
- """Health check especifico para a landing page Apokolips."""
364
- namespace = "apokolips-demo"
365
- logger.info("Verificando health check: apokolips-demo")
366
- typer.secho("\n=== Health Check: Apokolips Demo ===", fg=typer.colors.CYAN)
367
-
368
- pods_ok = check_k8s_pods_in_namespace(namespace, ctx, timeout=120)
369
- if not pods_ok:
370
- return False
371
- if ctx.dry_run:
372
- return True
373
-
374
- try:
375
- import json
376
-
377
- result = subprocess.run(
378
- [
379
- "kubectl",
380
- "get",
381
- "ingress",
382
- "apokolips-demo",
383
- "-n",
384
- namespace,
385
- "-o",
386
- "json",
387
- ],
388
- capture_output=True,
389
- text=True,
390
- timeout=10,
391
- )
392
- if result.returncode != 0:
393
- typer.secho(" ✗ Nao foi possivel consultar o ingress", fg=typer.colors.YELLOW)
394
- logger.warning("kubectl get ingress retornou codigo != 0 para apokolips-demo")
395
- return False
396
-
397
- data = json.loads(result.stdout)
398
- ingress_data = data.get("status", {}).get("loadBalancer", {}).get("ingress", [])
399
- address = ""
400
- if ingress_data:
401
- entry = ingress_data[0]
402
- address = entry.get("ip") or entry.get("hostname", "")
403
-
404
- if address:
405
- typer.secho(f" ✓ LoadBalancer publicado ({address})", fg=typer.colors.GREEN)
406
- return True
407
-
408
- typer.secho(" ✗ LoadBalancer ainda sem IP/hostname", fg=typer.colors.YELLOW)
409
- return False
410
- except Exception as exc:
411
- typer.secho(f" ✗ Erro ao verificar ingress: {exc}", fg=typer.colors.YELLOW)
412
- logger.error(f"Erro verificando ingress apokolips-demo: {exc}")
413
- return False
414
-
415
-
416
362
  # Mapeamento de modulos para funcoes de health check
417
363
  HEALTH_CHECKS = {
418
364
  "essentials": verify_essentials,
@@ -429,7 +375,7 @@ HEALTH_CHECKS = {
429
375
  "kafka": lambda ctx: verify_helm_chart("kafka", "kafka", ctx),
430
376
  "cert_manager": verify_cert_manager,
431
377
  "secrets": verify_secrets,
432
- "apokolips_demo": verify_apokolips_demo,
378
+
433
379
  }
434
380
 
435
381
 
@@ -0,0 +1,562 @@
1
+ """Utilitários centralizados para gerenciamento de usuários e políticas MinIO.
2
+
3
+ Este módulo implementa o princípio do menor privilégio para aplicações que
4
+ usam MinIO como backend de storage, criando usuários específicos com acesso
5
+ limitado aos buckets necessários.
6
+
7
+ Aplicações suportadas:
8
+ - vault-user → vault-storage bucket
9
+ - velero-user → velero-backups bucket
10
+ - harbor-user → harbor-registry, harbor-chartmuseum, harbor-jobservice buckets
11
+ - loki-user → loki-chunks, loki-ruler, loki-admin buckets (futuro)
12
+
13
+ Uso:
14
+ from raijin_server.minio_utils import get_or_create_minio_user
15
+
16
+ # Cria usuário específico para a aplicação
17
+ access_key, secret_key = get_or_create_minio_user(
18
+ ctx=ctx,
19
+ app_name="harbor",
20
+ buckets=["harbor-registry", "harbor-chartmuseum", "harbor-jobservice"],
21
+ )
22
+ """
23
+
24
+ import base64
25
+ import json
26
+ import secrets
27
+ import time
28
+ from pathlib import Path
29
+ from typing import Optional
30
+
31
+ import typer
32
+
33
+ from raijin_server.utils import ExecutionContext, run_cmd
34
+
35
+
36
+ # Configuração de usuários por aplicação
37
+ MINIO_APP_USERS = {
38
+ "vault": {
39
+ "username": "vault-user",
40
+ "buckets": ["vault-storage"],
41
+ "description": "Usuário para HashiCorp Vault backend storage",
42
+ },
43
+ "velero": {
44
+ "username": "velero-user",
45
+ "buckets": ["velero-backups"],
46
+ "description": "Usuário para Velero backup storage",
47
+ },
48
+ "harbor": {
49
+ "username": "harbor-user",
50
+ "buckets": ["harbor-registry", "harbor-chartmuseum", "harbor-jobservice"],
51
+ "description": "Usuário para Harbor container registry",
52
+ },
53
+ "loki": {
54
+ "username": "loki-user",
55
+ "buckets": ["loki-chunks", "loki-ruler", "loki-admin"],
56
+ "description": "Usuário para Loki logs storage",
57
+ },
58
+ }
59
+
60
+
61
+ def _generate_password(length: int = 32) -> str:
62
+ """Gera senha aleatória segura."""
63
+ return secrets.token_urlsafe(length)[:length]
64
+
65
+
66
+ def _get_minio_root_credentials(ctx: ExecutionContext) -> tuple[str, str]:
67
+ """Obtém credenciais root do MinIO do Secret do K8s."""
68
+ # Tenta secret 'minio' primeiro (padrão do Helm chart)
69
+ result = run_cmd(
70
+ ["kubectl", "-n", "minio", "get", "secret", "minio", "-o", "jsonpath={.data.rootUser}"],
71
+ ctx,
72
+ check=False,
73
+ )
74
+
75
+ if result.returncode == 0 and result.stdout:
76
+ root_user = base64.b64decode(result.stdout.strip()).decode("utf-8")
77
+
78
+ result = run_cmd(
79
+ ["kubectl", "-n", "minio", "get", "secret", "minio", "-o", "jsonpath={.data.rootPassword}"],
80
+ ctx,
81
+ check=False,
82
+ )
83
+
84
+ if result.returncode == 0 and result.stdout:
85
+ root_password = base64.b64decode(result.stdout.strip()).decode("utf-8")
86
+ return root_user, root_password
87
+
88
+ # Fallback para secret minio-credentials
89
+ result = run_cmd(
90
+ ["kubectl", "-n", "minio", "get", "secret", "minio-credentials", "-o", "jsonpath={.data.accesskey}"],
91
+ ctx,
92
+ check=False,
93
+ )
94
+
95
+ if result.returncode == 0 and result.stdout:
96
+ access_key = base64.b64decode(result.stdout.strip()).decode("utf-8")
97
+
98
+ result = run_cmd(
99
+ ["kubectl", "-n", "minio", "get", "secret", "minio-credentials", "-o", "jsonpath={.data.secretkey}"],
100
+ ctx,
101
+ check=False,
102
+ )
103
+
104
+ if result.returncode == 0 and result.stdout:
105
+ secret_key = base64.b64decode(result.stdout.strip()).decode("utf-8")
106
+ return access_key, secret_key
107
+
108
+ raise RuntimeError("Não foi possível obter credenciais root do MinIO. Verifique se o MinIO está instalado.")
109
+
110
+
111
+ def _setup_mc_alias(ctx: ExecutionContext, root_user: str, root_password: str) -> bool:
112
+ """Configura alias 'local' no mc dentro do pod MinIO."""
113
+ result = run_cmd(
114
+ [
115
+ "kubectl", "-n", "minio", "exec", "minio-0", "--",
116
+ "mc", "alias", "set", "local", "http://localhost:9000", root_user, root_password,
117
+ ],
118
+ ctx,
119
+ check=False,
120
+ )
121
+ return result.returncode == 0
122
+
123
+
124
+ def _check_user_exists(ctx: ExecutionContext, username: str) -> bool:
125
+ """Verifica se usuário já existe no MinIO."""
126
+ result = run_cmd(
127
+ ["kubectl", "-n", "minio", "exec", "minio-0", "--", "mc", "admin", "user", "info", "local", username],
128
+ ctx,
129
+ check=False,
130
+ )
131
+ return result.returncode == 0
132
+
133
+
134
+ def _create_minio_user(ctx: ExecutionContext, username: str, password: str) -> bool:
135
+ """Cria usuário no MinIO."""
136
+ result = run_cmd(
137
+ [
138
+ "kubectl", "-n", "minio", "exec", "minio-0", "--",
139
+ "mc", "admin", "user", "add", "local", username, password,
140
+ ],
141
+ ctx,
142
+ check=False,
143
+ )
144
+ return result.returncode == 0
145
+
146
+
147
+ def _create_bucket_policy(ctx: ExecutionContext, policy_name: str, buckets: list[str]) -> bool:
148
+ """Cria política de acesso restrita aos buckets especificados."""
149
+ # Cria documento de policy S3
150
+ policy_doc = {
151
+ "Version": "2012-10-17",
152
+ "Statement": [
153
+ {
154
+ "Effect": "Allow",
155
+ "Action": [
156
+ "s3:GetBucketLocation",
157
+ "s3:ListBucket",
158
+ "s3:ListBucketMultipartUploads",
159
+ ],
160
+ "Resource": [f"arn:aws:s3:::{bucket}" for bucket in buckets],
161
+ },
162
+ {
163
+ "Effect": "Allow",
164
+ "Action": [
165
+ "s3:GetObject",
166
+ "s3:PutObject",
167
+ "s3:DeleteObject",
168
+ "s3:ListMultipartUploadParts",
169
+ "s3:AbortMultipartUpload",
170
+ ],
171
+ "Resource": [f"arn:aws:s3:::{bucket}/*" for bucket in buckets],
172
+ },
173
+ ],
174
+ }
175
+
176
+ policy_json = json.dumps(policy_doc)
177
+
178
+ # Salva policy em arquivo temporário dentro do pod
179
+ result = run_cmd(
180
+ [
181
+ "kubectl", "-n", "minio", "exec", "minio-0", "--",
182
+ "sh", "-c", f"echo '{policy_json}' > /tmp/{policy_name}.json",
183
+ ],
184
+ ctx,
185
+ check=False,
186
+ )
187
+
188
+ if result.returncode != 0:
189
+ return False
190
+
191
+ # Cria policy no MinIO
192
+ result = run_cmd(
193
+ [
194
+ "kubectl", "-n", "minio", "exec", "minio-0", "--",
195
+ "mc", "admin", "policy", "create", "local", policy_name, f"/tmp/{policy_name}.json",
196
+ ],
197
+ ctx,
198
+ check=False,
199
+ )
200
+
201
+ # Se policy já existe, atualiza
202
+ if result.returncode != 0:
203
+ result = run_cmd(
204
+ [
205
+ "kubectl", "-n", "minio", "exec", "minio-0", "--",
206
+ "mc", "admin", "policy", "remove", "local", policy_name,
207
+ ],
208
+ ctx,
209
+ check=False,
210
+ )
211
+ result = run_cmd(
212
+ [
213
+ "kubectl", "-n", "minio", "exec", "minio-0", "--",
214
+ "mc", "admin", "policy", "create", "local", policy_name, f"/tmp/{policy_name}.json",
215
+ ],
216
+ ctx,
217
+ check=False,
218
+ )
219
+
220
+ return result.returncode == 0
221
+
222
+
223
+ def _attach_policy_to_user(ctx: ExecutionContext, username: str, policy_name: str) -> bool:
224
+ """Associa política ao usuário."""
225
+ result = run_cmd(
226
+ [
227
+ "kubectl", "-n", "minio", "exec", "minio-0", "--",
228
+ "mc", "admin", "policy", "attach", "local", policy_name, "--user", username,
229
+ ],
230
+ ctx,
231
+ check=False,
232
+ )
233
+ return result.returncode == 0
234
+
235
+
236
+ def _create_bucket(ctx: ExecutionContext, bucket_name: str) -> bool:
237
+ """Cria bucket se não existir."""
238
+ # Verifica se bucket existe
239
+ result = run_cmd(
240
+ ["kubectl", "-n", "minio", "exec", "minio-0", "--", "mc", "ls", f"local/{bucket_name}"],
241
+ ctx,
242
+ check=False,
243
+ )
244
+
245
+ if result.returncode == 0:
246
+ typer.echo(f" Bucket '{bucket_name}' já existe.")
247
+ return True
248
+
249
+ # Cria bucket
250
+ result = run_cmd(
251
+ ["kubectl", "-n", "minio", "exec", "minio-0", "--", "mc", "mb", f"local/{bucket_name}"],
252
+ ctx,
253
+ check=False,
254
+ )
255
+
256
+ if result.returncode == 0:
257
+ typer.secho(f" ✓ Bucket '{bucket_name}' criado.", fg=typer.colors.GREEN)
258
+ return True
259
+
260
+ typer.secho(f" ✗ Falha ao criar bucket '{bucket_name}'.", fg=typer.colors.RED)
261
+ return False
262
+
263
+
264
+ def _save_credentials_to_k8s_secret(
265
+ ctx: ExecutionContext,
266
+ app_name: str,
267
+ username: str,
268
+ password: str,
269
+ namespace: str,
270
+ ) -> bool:
271
+ """Salva credenciais do usuário em um Secret K8s."""
272
+ import subprocess
273
+ import tempfile
274
+ from pathlib import Path
275
+
276
+ secret_name = f"minio-{app_name}-credentials"
277
+
278
+ # Cria ou atualiza secret
279
+ secret_manifest = f"""apiVersion: v1
280
+ kind: Secret
281
+ metadata:
282
+ name: {secret_name}
283
+ namespace: {namespace}
284
+ labels:
285
+ app.kubernetes.io/managed-by: raijin-server
286
+ app.kubernetes.io/component: minio-credentials
287
+ app.kubernetes.io/part-of: {app_name}
288
+ type: Opaque
289
+ stringData:
290
+ accesskey: "{username}"
291
+ secretkey: "{password}"
292
+ AWS_ACCESS_KEY_ID: "{username}"
293
+ AWS_SECRET_ACCESS_KEY: "{password}"
294
+ """
295
+
296
+ # Usa arquivo temporário para aplicar o manifest
297
+ tmp_path = None
298
+ try:
299
+ with tempfile.NamedTemporaryFile("w", delete=False, suffix=".yaml") as tmp:
300
+ tmp.write(secret_manifest)
301
+ tmp.flush()
302
+ tmp_path = Path(tmp.name)
303
+
304
+ result = run_cmd(
305
+ ["kubectl", "apply", "-f", str(tmp_path)],
306
+ ctx,
307
+ check=False,
308
+ )
309
+ return result.returncode == 0
310
+ finally:
311
+ if tmp_path and tmp_path.exists():
312
+ tmp_path.unlink(missing_ok=True)
313
+
314
+
315
+ def _get_credentials_from_k8s_secret(
316
+ ctx: ExecutionContext,
317
+ app_name: str,
318
+ namespace: str,
319
+ ) -> Optional[tuple[str, str]]:
320
+ """Recupera credenciais do Secret K8s se existir."""
321
+ secret_name = f"minio-{app_name}-credentials"
322
+
323
+ result = run_cmd(
324
+ ["kubectl", "-n", namespace, "get", "secret", secret_name, "-o", "jsonpath={.data.accesskey}"],
325
+ ctx,
326
+ check=False,
327
+ )
328
+
329
+ if result.returncode == 0 and result.stdout:
330
+ access_key = base64.b64decode(result.stdout.strip()).decode("utf-8")
331
+
332
+ result = run_cmd(
333
+ ["kubectl", "-n", namespace, "get", "secret", secret_name, "-o", "jsonpath={.data.secretkey}"],
334
+ ctx,
335
+ check=False,
336
+ )
337
+
338
+ if result.returncode == 0 and result.stdout:
339
+ secret_key = base64.b64decode(result.stdout.strip()).decode("utf-8")
340
+ return access_key, secret_key
341
+
342
+ return None
343
+
344
+
345
+ def get_or_create_minio_user(
346
+ ctx: ExecutionContext,
347
+ app_name: str,
348
+ buckets: Optional[list[str]] = None,
349
+ namespace: Optional[str] = None,
350
+ force_recreate: bool = False,
351
+ ) -> tuple[str, str]:
352
+ """Obtém ou cria usuário MinIO específico para uma aplicação.
353
+
354
+ Esta função implementa o princípio do menor privilégio, criando um usuário
355
+ MinIO com acesso restrito apenas aos buckets necessários para a aplicação.
356
+
357
+ Args:
358
+ ctx: Contexto de execução
359
+ app_name: Nome da aplicação (vault, velero, harbor, loki)
360
+ buckets: Lista de buckets para criar/permitir acesso (opcional, usa padrão se não fornecido)
361
+ namespace: Namespace K8s para salvar o secret (opcional, usa app_name se não fornecido)
362
+ force_recreate: Se True, recria usuário mesmo se já existir
363
+
364
+ Returns:
365
+ Tupla (access_key, secret_key) com as credenciais do usuário
366
+
367
+ Raises:
368
+ RuntimeError: Se não conseguir criar usuário ou MinIO não estiver disponível
369
+ """
370
+ typer.echo(f"\n🔐 Configurando usuário MinIO para '{app_name}'...")
371
+
372
+ # Obtém configuração padrão se não fornecida
373
+ if app_name in MINIO_APP_USERS:
374
+ config = MINIO_APP_USERS[app_name]
375
+ username = config["username"]
376
+ if buckets is None:
377
+ buckets = config["buckets"]
378
+ if namespace is None:
379
+ namespace = app_name
380
+ else:
381
+ username = f"{app_name}-user"
382
+ if buckets is None:
383
+ buckets = [f"{app_name}-data"]
384
+ if namespace is None:
385
+ namespace = app_name
386
+
387
+ policy_name = f"{app_name}-policy"
388
+
389
+ # Verifica se já existe secret com credenciais
390
+ if not force_recreate:
391
+ existing_creds = _get_credentials_from_k8s_secret(ctx, app_name, namespace)
392
+ if existing_creds:
393
+ typer.secho(f" ✓ Credenciais existentes encontradas para '{app_name}'.", fg=typer.colors.GREEN)
394
+ return existing_creds
395
+
396
+ if ctx.dry_run:
397
+ typer.echo(f" [DRY-RUN] Criaria usuário '{username}' com acesso a: {', '.join(buckets)}")
398
+ return (username, "dry-run-password")
399
+
400
+ # Obtém credenciais root
401
+ try:
402
+ root_user, root_password = _get_minio_root_credentials(ctx)
403
+ except RuntimeError as e:
404
+ typer.secho(f" ✗ {e}", fg=typer.colors.RED)
405
+ raise
406
+
407
+ # Configura mc alias
408
+ if not _setup_mc_alias(ctx, root_user, root_password):
409
+ raise RuntimeError("Falha ao configurar mc alias no MinIO.")
410
+
411
+ # Cria buckets necessários
412
+ typer.echo(f" Criando buckets para '{app_name}'...")
413
+ for bucket in buckets:
414
+ _create_bucket(ctx, bucket)
415
+
416
+ # Verifica se usuário já existe
417
+ user_exists = _check_user_exists(ctx, username)
418
+
419
+ if user_exists and not force_recreate:
420
+ # Recupera senha existente do secret se disponível
421
+ existing = _get_credentials_from_k8s_secret(ctx, app_name, namespace)
422
+ if existing:
423
+ typer.secho(f" ✓ Usuário '{username}' já existe.", fg=typer.colors.GREEN)
424
+ return existing
425
+
426
+ # Gera nova senha
427
+ password = _generate_password(32)
428
+
429
+ # Cria ou recria usuário
430
+ if user_exists:
431
+ typer.echo(f" Recriando usuário '{username}'...")
432
+ # Remove usuário antigo
433
+ run_cmd(
434
+ ["kubectl", "-n", "minio", "exec", "minio-0", "--", "mc", "admin", "user", "remove", "local", username],
435
+ ctx,
436
+ check=False,
437
+ )
438
+
439
+ typer.echo(f" Criando usuário '{username}'...")
440
+ if not _create_minio_user(ctx, username, password):
441
+ raise RuntimeError(f"Falha ao criar usuário MinIO '{username}'.")
442
+
443
+ # Cria política de acesso
444
+ typer.echo(f" Criando política '{policy_name}'...")
445
+ if not _create_bucket_policy(ctx, policy_name, buckets):
446
+ raise RuntimeError(f"Falha ao criar política '{policy_name}'.")
447
+
448
+ # Associa política ao usuário
449
+ typer.echo(f" Associando política ao usuário...")
450
+ if not _attach_policy_to_user(ctx, username, policy_name):
451
+ raise RuntimeError(f"Falha ao associar política ao usuário '{username}'.")
452
+
453
+ # Garante que namespace existe
454
+ run_cmd(
455
+ ["kubectl", "create", "namespace", namespace, "--dry-run=client", "-o", "yaml"],
456
+ ctx,
457
+ check=False,
458
+ )
459
+ run_cmd(
460
+ ["kubectl", "create", "namespace", namespace],
461
+ ctx,
462
+ check=False,
463
+ )
464
+
465
+ # Salva credenciais em Secret K8s
466
+ typer.echo(f" Salvando credenciais em Secret K8s...")
467
+ if not _save_credentials_to_k8s_secret(ctx, app_name, username, password, namespace):
468
+ typer.secho(f" ⚠️ Falha ao salvar secret, mas usuário foi criado.", fg=typer.colors.YELLOW)
469
+
470
+ typer.secho(f" ✓ Usuário '{username}' criado com acesso a: {', '.join(buckets)}", fg=typer.colors.GREEN)
471
+
472
+ return (username, password)
473
+
474
+
475
+ def list_minio_users(ctx: ExecutionContext) -> list[dict]:
476
+ """Lista todos os usuários MinIO (exceto root)."""
477
+ try:
478
+ root_user, root_password = _get_minio_root_credentials(ctx)
479
+ _setup_mc_alias(ctx, root_user, root_password)
480
+ except RuntimeError:
481
+ return []
482
+
483
+ result = run_cmd(
484
+ ["kubectl", "-n", "minio", "exec", "minio-0", "--", "mc", "admin", "user", "ls", "local", "--json"],
485
+ ctx,
486
+ check=False,
487
+ )
488
+
489
+ if result.returncode != 0:
490
+ return []
491
+
492
+ users = []
493
+ for line in (result.stdout or "").strip().split("\n"):
494
+ if line.strip():
495
+ try:
496
+ data = json.loads(line)
497
+ if data.get("accessKey"):
498
+ users.append({
499
+ "username": data.get("accessKey"),
500
+ "status": data.get("userStatus", "enabled"),
501
+ "policy": data.get("policyName", ""),
502
+ })
503
+ except json.JSONDecodeError:
504
+ pass
505
+
506
+ return users
507
+
508
+
509
+ def delete_minio_user(ctx: ExecutionContext, app_name: str) -> bool:
510
+ """Remove usuário MinIO e seu secret associado."""
511
+ if app_name in MINIO_APP_USERS:
512
+ username = MINIO_APP_USERS[app_name]["username"]
513
+ else:
514
+ username = f"{app_name}-user"
515
+
516
+ policy_name = f"{app_name}-policy"
517
+ secret_name = f"minio-{app_name}-credentials"
518
+
519
+ typer.echo(f"Removendo usuário MinIO '{username}'...")
520
+
521
+ try:
522
+ root_user, root_password = _get_minio_root_credentials(ctx)
523
+ _setup_mc_alias(ctx, root_user, root_password)
524
+ except RuntimeError as e:
525
+ typer.secho(f" ✗ {e}", fg=typer.colors.RED)
526
+ return False
527
+
528
+ # Remove associação de política
529
+ run_cmd(
530
+ [
531
+ "kubectl", "-n", "minio", "exec", "minio-0", "--",
532
+ "mc", "admin", "policy", "detach", "local", policy_name, "--user", username,
533
+ ],
534
+ ctx,
535
+ check=False,
536
+ )
537
+
538
+ # Remove usuário
539
+ result = run_cmd(
540
+ ["kubectl", "-n", "minio", "exec", "minio-0", "--", "mc", "admin", "user", "remove", "local", username],
541
+ ctx,
542
+ check=False,
543
+ )
544
+
545
+ if result.returncode == 0:
546
+ typer.secho(f" ✓ Usuário '{username}' removido.", fg=typer.colors.GREEN)
547
+
548
+ # Remove política
549
+ run_cmd(
550
+ ["kubectl", "-n", "minio", "exec", "minio-0", "--", "mc", "admin", "policy", "remove", "local", policy_name],
551
+ ctx,
552
+ check=False,
553
+ )
554
+
555
+ # Remove secret K8s
556
+ run_cmd(
557
+ ["kubectl", "-n", app_name, "delete", "secret", secret_name, "--ignore-not-found"],
558
+ ctx,
559
+ check=False,
560
+ )
561
+
562
+ return True
@@ -24,7 +24,6 @@ __all__ = [
24
24
  "vpn",
25
25
  "vpn_client",
26
26
  "internal_dns",
27
- "apokolips_demo",
28
27
  "cert_manager",
29
28
  "secrets",
30
29
  "full_install",
@@ -32,5 +31,5 @@ __all__ = [
32
31
 
33
32
  from raijin_server.modules import calico, essentials, firewall, grafana, harbor, harness, hardening, istio
34
33
  from raijin_server.modules import kafka, kong, kubernetes, loki, minio, network
35
- from raijin_server.modules import prometheus, traefik, velero, apokolips_demo, secrets, cert_manager
34
+ from raijin_server.modules import prometheus, traefik, velero, secrets, cert_manager
36
35
  from raijin_server.modules import bootstrap, full_install, sanitize, ssh_hardening, vpn, vpn_client, internal_dns