raijin-server 0.3.4__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.

Potentially problematic release.


This version of raijin-server might be problematic. Click here for more details.

@@ -1,10 +1,15 @@
1
- """Automacao de sealed-secrets e external-secrets via Helm (production-ready).
1
+ """Automacao de HashiCorp Vault e External Secrets Operator (production-ready).
2
2
 
3
- Instala os controladores necessários para criptografar e consumir segredos
4
- em clusters Kubernetes. Inclui opcionalmente a exportacao do certificado
5
- publico do sealed-secrets para permitir geracao de manifests lacrados.
3
+ Instala Vault com MinIO backend para persistencia e External Secrets Operator
4
+ para sincronizar segredos do Vault para Secrets nativos do Kubernetes.
5
+
6
+ Arquitetura:
7
+ - Vault: Gerenciamento centralizado de segredos com MinIO como storage backend
8
+ - External Secrets Operator: Sincroniza segredos do Vault para K8s Secrets
9
+ - Aplicações: Usam Secrets nativos do K8s (transparente)
6
10
  """
7
11
 
12
+ import base64
8
13
  import socket
9
14
  import time
10
15
  from pathlib import Path
@@ -19,8 +24,9 @@ from raijin_server.utils import (
19
24
  run_cmd,
20
25
  write_file,
21
26
  )
27
+ from raijin_server.minio_utils import get_or_create_minio_user
22
28
 
23
- SEALED_NAMESPACE = "kube-system"
29
+ VAULT_NAMESPACE = "vault"
24
30
  ESO_NAMESPACE = "external-secrets"
25
31
 
26
32
 
@@ -36,10 +42,10 @@ def _detect_node_name(ctx: ExecutionContext) -> str:
36
42
  return socket.gethostname()
37
43
 
38
44
 
39
- def _check_existing_sealed_secrets(ctx: ExecutionContext, namespace: str) -> bool:
40
- """Verifica se existe instalacao do Sealed Secrets."""
45
+ def _check_existing_vault(ctx: ExecutionContext, namespace: str) -> bool:
46
+ """Verifica se existe instalacao do Vault."""
41
47
  result = run_cmd(
42
- ["helm", "status", "sealed-secrets", "-n", namespace],
48
+ ["helm", "status", "vault", "-n", namespace],
43
49
  ctx,
44
50
  check=False,
45
51
  )
@@ -56,12 +62,12 @@ def _check_existing_external_secrets(ctx: ExecutionContext, namespace: str) -> b
56
62
  return result.returncode == 0
57
63
 
58
64
 
59
- def _uninstall_sealed_secrets(ctx: ExecutionContext, namespace: str) -> None:
60
- """Remove instalacao anterior do Sealed Secrets."""
61
- typer.echo("Removendo instalacao anterior do Sealed Secrets...")
65
+ def _uninstall_vault(ctx: ExecutionContext, namespace: str) -> None:
66
+ """Remove instalacao anterior do Vault."""
67
+ typer.echo("Removendo instalacao anterior do Vault...")
62
68
 
63
69
  run_cmd(
64
- ["helm", "uninstall", "sealed-secrets", "-n", namespace],
70
+ ["helm", "uninstall", "vault", "-n", namespace],
65
71
  ctx,
66
72
  check=False,
67
73
  )
@@ -82,16 +88,16 @@ def _uninstall_external_secrets(ctx: ExecutionContext, namespace: str) -> None:
82
88
  time.sleep(5)
83
89
 
84
90
 
85
- def _wait_for_sealed_secrets_ready(ctx: ExecutionContext, namespace: str, timeout: int = 120) -> bool:
86
- """Aguarda pods do Sealed Secrets ficarem Ready."""
87
- typer.echo("Aguardando pods do Sealed Secrets ficarem Ready...")
91
+ def _wait_for_pods_ready(ctx: ExecutionContext, namespace: str, label: str, timeout: int = 120) -> bool:
92
+ """Aguarda pods ficarem Ready."""
93
+ typer.echo(f"Aguardando pods com label {label} ficarem Ready...")
88
94
  deadline = time.time() + timeout
89
95
 
90
96
  while time.time() < deadline:
91
97
  result = run_cmd(
92
98
  [
93
99
  "kubectl", "-n", namespace, "get", "pods",
94
- "-l", "app.kubernetes.io/name=sealed-secrets",
100
+ "-l", label,
95
101
  "-o", "jsonpath={range .items[*]}{.metadata.name}={.status.phase} {end}",
96
102
  ],
97
103
  ctx,
@@ -109,49 +115,248 @@ def _wait_for_sealed_secrets_ready(ctx: ExecutionContext, namespace: str, timeou
109
115
  pods.append((parts[0], parts[1]))
110
116
 
111
117
  if pods and all(phase == "Running" for _, phase in pods):
112
- typer.secho(" Sealed Secrets Ready.", fg=typer.colors.GREEN)
118
+ typer.secho(f" Pods {label} Ready.", fg=typer.colors.GREEN)
113
119
  return True
114
120
 
115
121
  time.sleep(5)
116
122
 
117
- typer.secho(" Timeout aguardando Sealed Secrets.", fg=typer.colors.YELLOW)
123
+ typer.secho(f" Timeout aguardando pods {label}.", fg=typer.colors.YELLOW)
118
124
  return False
119
125
 
120
126
 
121
- def _export_sealed_cert(namespace: str, ctx: ExecutionContext) -> None:
122
- """Exporta o certificado publico do sealed-secrets para um caminho local."""
127
+ def _get_minio_credentials(ctx: ExecutionContext) -> tuple[str, str]:
128
+ """Obtem ou cria credenciais específicas do MinIO para Vault.
129
+
130
+ Esta função cria um usuário MinIO dedicado para o Vault com acesso
131
+ restrito apenas ao bucket 'vault-storage'.
132
+ """
133
+ return get_or_create_minio_user(
134
+ ctx=ctx,
135
+ app_name="vault",
136
+ buckets=["vault-storage"],
137
+ namespace=VAULT_NAMESPACE,
138
+ )
139
+
123
140
 
124
- default_path = Path("/tmp/sealed-secrets-cert.pem")
125
- dest = typer.prompt(
126
- "Caminho para salvar o certificado publico do sealed-secrets",
127
- default=str(default_path),
141
+ def _initialize_vault(ctx: ExecutionContext, vault_ns: str, node_ip: str) -> tuple[str, list[str]]:
142
+ """Inicializa o Vault e retorna root token e unseal keys."""
143
+ typer.echo("\n Inicializando Vault...")
144
+
145
+ result = run_cmd(
146
+ ["kubectl", "-n", vault_ns, "exec", "vault-0", "--", "vault", "operator", "init", "-format=json"],
147
+ ctx,
148
+ check=False,
128
149
  )
129
- typer.echo(f"Exportando certificado para {dest}...")
130
- cmd = [
131
- "kubectl",
132
- "-n",
133
- namespace,
134
- "get",
135
- "secret",
136
- "-l",
137
- "sealedsecrets.bitnami.com/sealed-secrets-key",
138
- "-o",
139
- r"jsonpath={.items[0].data.tls\.crt}",
140
- ]
141
- result = run_cmd(cmd, ctx, check=False)
150
+
142
151
  if result.returncode != 0:
143
- typer.secho("Nao foi possivel obter o certificado (tente novamente apos o pod estar Ready).", fg=typer.colors.YELLOW)
144
- return
152
+ typer.secho("Falha ao inicializar Vault.", fg=typer.colors.RED)
153
+ raise typer.Exit(1)
154
+
155
+ import json
156
+ init_data = json.loads(result.stdout)
157
+ root_token = init_data["root_token"]
158
+ unseal_keys = init_data["unseal_keys_b64"]
159
+
160
+ # Salva keys localmente
161
+ vault_keys_path = Path("/etc/vault/keys.json")
162
+ vault_keys_path.parent.mkdir(parents=True, exist_ok=True)
163
+ vault_keys_path.write_text(json.dumps(init_data, indent=2))
164
+ typer.secho(f"\n✓ Vault keys salvas em {vault_keys_path}", fg=typer.colors.GREEN)
165
+ typer.secho("⚠️ IMPORTANTE: Guarde essas keys em local seguro!", fg=typer.colors.YELLOW, bold=True)
166
+
167
+ return root_token, unseal_keys
168
+
169
+
170
+ def _unseal_vault(ctx: ExecutionContext, vault_ns: str, unseal_keys: list[str]) -> None:
171
+ """Destrava o Vault usando as unseal keys."""
172
+ typer.echo("\nDesbloqueando Vault...")
173
+
174
+ # Precisa de 3 keys das 5 geradas (threshold padrão)
175
+ for i in range(3):
176
+ run_cmd(
177
+ ["kubectl", "-n", vault_ns, "exec", "vault-0", "--", "vault", "operator", "unseal", unseal_keys[i]],
178
+ ctx,
179
+ )
180
+
181
+ typer.secho("✓ Vault desbloqueado.", fg=typer.colors.GREEN)
182
+
183
+
184
+ def _enable_kv_secrets(ctx: ExecutionContext, vault_ns: str, root_token: str) -> None:
185
+ """Habilita KV v2 secrets engine."""
186
+ typer.echo("\nHabilitando KV v2 secrets engine...")
187
+
188
+ run_cmd(
189
+ [
190
+ "kubectl", "-n", vault_ns, "exec", "vault-0", "--",
191
+ "vault", "secrets", "enable", "-path=secret", "kv-v2"
192
+ ],
193
+ ctx,
194
+ env={"VAULT_TOKEN": root_token},
195
+ check=False, # Pode já estar habilitado
196
+ )
197
+
198
+ typer.secho("✓ KV v2 habilitado em path 'secret'.", fg=typer.colors.GREEN)
199
+
200
+
201
+ def _configure_kubernetes_auth(ctx: ExecutionContext, vault_ns: str, root_token: str) -> None:
202
+ """Configura autenticação Kubernetes no Vault."""
203
+ typer.echo("\nConfigurando autenticação Kubernetes...")
204
+
205
+ # Habilita kubernetes auth
206
+ run_cmd(
207
+ ["kubectl", "-n", vault_ns, "exec", "vault-0", "--", "vault", "auth", "enable", "kubernetes"],
208
+ ctx,
209
+ env={"VAULT_TOKEN": root_token},
210
+ check=False,
211
+ )
212
+
213
+ # Configura kubernetes auth
214
+ run_cmd(
215
+ [
216
+ "kubectl", "-n", vault_ns, "exec", "vault-0", "--",
217
+ "sh", "-c",
218
+ "vault write auth/kubernetes/config " +
219
+ "kubernetes_host=https://$KUBERNETES_PORT_443_TCP_ADDR:443"
220
+ ],
221
+ ctx,
222
+ env={"VAULT_TOKEN": root_token},
223
+ )
224
+
225
+ typer.secho("✓ Autenticação Kubernetes configurada.", fg=typer.colors.GREEN)
145
226
 
146
- try:
147
- import base64
148
227
 
149
- cert_b64 = result.stdout.strip()
150
- cert_bytes = base64.b64decode(cert_b64)
151
- Path(dest).write_bytes(cert_bytes)
152
- typer.secho(f"✓ Certificado salvo em {dest}", fg=typer.colors.GREEN)
153
- except Exception as exc:
154
- typer.secho(f"Falha ao decodificar/salvar certificado: {exc}", fg=typer.colors.YELLOW)
228
+ def _create_eso_policy_and_role(ctx: ExecutionContext, vault_ns: str, root_token: str, eso_ns: str) -> None:
229
+ """Cria policy e role para External Secrets Operator."""
230
+ typer.echo("\nCriando policy e role para ESO...")
231
+
232
+ # Policy para ler todos os secrets
233
+ policy = """path "secret/data/*" {
234
+ capabilities = ["read"]
235
+ }"""
236
+
237
+ run_cmd(
238
+ ["kubectl", "-n", vault_ns, "exec", "vault-0", "--", "vault", "policy", "write", "eso-policy", "-"],
239
+ ctx,
240
+ env={"VAULT_TOKEN": root_token},
241
+ input=policy,
242
+ )
243
+
244
+ # Role vinculando serviceaccount do ESO
245
+ run_cmd(
246
+ [
247
+ "kubectl", "-n", vault_ns, "exec", "vault-0", "--",
248
+ "vault", "write", "auth/kubernetes/role/eso-role",
249
+ "bound_service_account_names=external-secrets",
250
+ f"bound_service_account_namespaces={eso_ns}",
251
+ "policies=eso-policy",
252
+ "ttl=24h"
253
+ ],
254
+ ctx,
255
+ env={"VAULT_TOKEN": root_token},
256
+ )
257
+
258
+ typer.secho("✓ Policy 'eso-policy' e role 'eso-role' criadas.", fg=typer.colors.GREEN)
259
+
260
+
261
+ def _create_secretstore_example(ctx: ExecutionContext, vault_ns: str, eso_ns: str, node_ip: str) -> None:
262
+ """Cria exemplo de ClusterSecretStore e ExternalSecret."""
263
+ typer.echo("\nCriando exemplo de ClusterSecretStore...")
264
+
265
+ secretstore_yaml = f"""apiVersion: external-secrets.io/v1beta1
266
+ kind: ClusterSecretStore
267
+ metadata:
268
+ name: vault-backend
269
+ spec:
270
+ provider:
271
+ vault:
272
+ server: "http://vault.{vault_ns}.svc.cluster.local:8200"
273
+ path: "secret"
274
+ version: "v2"
275
+ auth:
276
+ kubernetes:
277
+ mountPath: "kubernetes"
278
+ role: "eso-role"
279
+ serviceAccountRef:
280
+ name: "external-secrets"
281
+ namespace: "{eso_ns}"
282
+ """
283
+
284
+ secretstore_path = Path("/tmp/raijin-vault-secretstore.yaml")
285
+ write_file(secretstore_path, secretstore_yaml, ctx)
286
+
287
+ run_cmd(
288
+ ["kubectl", "apply", "-f", str(secretstore_path)],
289
+ ctx,
290
+ )
291
+
292
+ typer.secho("✓ ClusterSecretStore 'vault-backend' criado.", fg=typer.colors.GREEN)
293
+
294
+
295
+ def _create_example_secret(ctx: ExecutionContext, vault_ns: str, root_token: str) -> None:
296
+ """Cria um secret de exemplo no Vault."""
297
+ typer.echo("\nCriando secret de exemplo no Vault...")
298
+
299
+ run_cmd(
300
+ [
301
+ "kubectl", "-n", vault_ns, "exec", "vault-0", "--",
302
+ "vault", "kv", "put", "secret/example",
303
+ "username=admin",
304
+ "password=supersecret123"
305
+ ],
306
+ ctx,
307
+ env={"VAULT_TOKEN": root_token},
308
+ )
309
+
310
+ typer.secho("✓ Secret 'secret/example' criado no Vault.", fg=typer.colors.GREEN)
311
+
312
+ # Cria ExternalSecret de exemplo
313
+ external_secret_yaml = """apiVersion: external-secrets.io/v1beta1
314
+ kind: ExternalSecret
315
+ metadata:
316
+ name: example-secret
317
+ namespace: default
318
+ spec:
319
+ refreshInterval: 1h
320
+ secretStoreRef:
321
+ name: vault-backend
322
+ kind: ClusterSecretStore
323
+ target:
324
+ name: example-secret
325
+ creationPolicy: Owner
326
+ data:
327
+ - secretKey: username
328
+ remoteRef:
329
+ key: secret/example
330
+ property: username
331
+ - secretKey: password
332
+ remoteRef:
333
+ key: secret/example
334
+ property: password
335
+ """
336
+
337
+ external_secret_path = Path("/tmp/raijin-vault-externalsecret.yaml")
338
+ write_file(external_secret_path, external_secret_yaml, ctx)
339
+
340
+ run_cmd(
341
+ ["kubectl", "apply", "-f", str(external_secret_path)],
342
+ ctx,
343
+ )
344
+
345
+ typer.secho("✓ ExternalSecret 'example-secret' criado no namespace default.", fg=typer.colors.GREEN)
346
+
347
+ # Aguarda sincronização
348
+ time.sleep(5)
349
+
350
+ # Verifica se o Secret foi criado
351
+ result = run_cmd(
352
+ ["kubectl", "-n", "default", "get", "secret", "example-secret"],
353
+ ctx,
354
+ check=False,
355
+ )
356
+
357
+ if result.returncode == 0:
358
+ typer.secho("\n✓ Secret sincronizado com sucesso! Teste com:", fg=typer.colors.GREEN)
359
+ typer.echo(" kubectl -n default get secret example-secret -o yaml")
155
360
 
156
361
 
157
362
  def run(ctx: ExecutionContext) -> None:
@@ -159,69 +364,124 @@ def run(ctx: ExecutionContext) -> None:
159
364
  ensure_tool("kubectl", ctx, install_hint="Instale kubectl ou habilite dry-run.")
160
365
  ensure_tool("helm", ctx, install_hint="Instale helm ou habilite dry-run.")
161
366
 
162
- typer.echo("Instalando sealed-secrets e external-secrets...")
367
+ typer.echo("Instalando HashiCorp Vault + External Secrets Operator...")
163
368
 
164
- sealed_ns = typer.prompt("Namespace para sealed-secrets", default=SEALED_NAMESPACE)
165
- eso_ns = typer.prompt("Namespace para external-secrets", default=ESO_NAMESPACE)
369
+ vault_ns = typer.prompt("Namespace para Vault", default=VAULT_NAMESPACE)
370
+ eso_ns = typer.prompt("Namespace para External Secrets", default=ESO_NAMESPACE)
166
371
 
167
372
  node_name = _detect_node_name(ctx)
373
+
374
+ # Detecta IP do node para acesso ao MinIO
375
+ result = run_cmd(
376
+ ["kubectl", "get", "nodes", "-o", "jsonpath={.items[0].status.addresses[?(@.type=='InternalIP')].address}"],
377
+ ctx,
378
+ check=False,
379
+ )
380
+ node_ip = result.stdout.strip() if result.returncode == 0 else "192.168.1.81"
381
+
382
+ minio_host = typer.prompt("MinIO host", default=f"{node_ip}:30900")
383
+ access_key, secret_key = _get_minio_credentials(ctx)
168
384
 
169
- # sealed-secrets
170
- typer.secho("\n== Sealed Secrets ==", fg=typer.colors.CYAN, bold=True)
385
+ # ========== HashiCorp Vault ==========
386
+ typer.secho("\n== HashiCorp Vault ==", fg=typer.colors.CYAN, bold=True)
171
387
 
172
- # Prompt opcional de limpeza
173
- if _check_existing_sealed_secrets(ctx, sealed_ns):
388
+ if _check_existing_vault(ctx, vault_ns):
174
389
  cleanup = typer.confirm(
175
- "Instalacao anterior do Sealed Secrets detectada. Limpar antes de reinstalar?",
390
+ "Instalacao anterior do Vault detectada. Limpar antes de reinstalar?",
176
391
  default=False,
177
392
  )
178
393
  if cleanup:
179
- _uninstall_sealed_secrets(ctx, sealed_ns)
180
-
181
- sealed_values_yaml = f"""tolerations:
182
- - key: node-role.kubernetes.io/control-plane
183
- operator: Exists
184
- effect: NoSchedule
185
- - key: node-role.kubernetes.io/master
186
- operator: Exists
187
- effect: NoSchedule
188
- nodeSelector:
189
- kubernetes.io/hostname: {node_name}
190
- resources:
191
- requests:
192
- memory: 64Mi
193
- cpu: 50m
194
- limits:
195
- memory: 128Mi
394
+ _uninstall_vault(ctx, vault_ns)
395
+
396
+ # Credenciais são obtidas automaticamente via get_or_create_minio_user
397
+ # que já cria o bucket 'vault-storage' e o usuário 'vault-user'
398
+
399
+ vault_values_yaml = f"""server:
400
+ ha:
401
+ enabled: true
402
+ replicas: 1
403
+ raft:
404
+ enabled: false
405
+
406
+ standalone:
407
+ enabled: true
408
+ config: |
409
+ ui = true
410
+
411
+ listener "tcp" {{
412
+ tls_disable = 1
413
+ address = "[::]:8200"
414
+ cluster_address = "[::]:8201"
415
+ }}
416
+
417
+ storage "s3" {{
418
+ endpoint = "http://{minio_host}"
419
+ bucket = "vault-storage"
420
+ access_key = "{access_key}"
421
+ secret_key = "{secret_key}"
422
+ s3_force_path_style = true
423
+ }}
424
+
425
+ api_addr = "http://vault.{vault_ns}.svc.cluster.local:8200"
426
+ cluster_addr = "http://vault-0.vault-internal:8201"
427
+
428
+ tolerations:
429
+ - key: node-role.kubernetes.io/control-plane
430
+ operator: Exists
431
+ effect: NoSchedule
432
+ - key: node-role.kubernetes.io/master
433
+ operator: Exists
434
+ effect: NoSchedule
435
+
436
+ nodeSelector:
437
+ kubernetes.io/hostname: {node_name}
438
+
439
+ resources:
440
+ requests:
441
+ memory: 256Mi
442
+ cpu: 250m
443
+ limits:
444
+ memory: 512Mi
445
+
446
+ ui:
447
+ enabled: true
448
+ serviceType: "NodePort"
449
+ serviceNodePort: 30820
450
+
451
+ injector:
452
+ enabled: false
196
453
  """
197
454
 
198
- sealed_values_path = Path("/tmp/raijin-sealed-secrets-values.yaml")
199
- write_file(sealed_values_path, sealed_values_yaml, ctx)
455
+ vault_values_path = Path("/tmp/raijin-vault-values.yaml")
456
+ write_file(vault_values_path, vault_values_yaml, ctx)
200
457
 
201
458
  helm_upgrade_install(
202
- "sealed-secrets",
203
- "sealed-secrets",
204
- sealed_ns,
459
+ "vault",
460
+ "vault",
461
+ vault_ns,
205
462
  ctx,
206
- repo="bitnami-labs",
207
- repo_url="https://bitnami-labs.github.io/sealed-secrets",
463
+ repo="hashicorp",
464
+ repo_url="https://helm.releases.hashicorp.com",
208
465
  create_namespace=True,
209
- extra_args=["-f", str(sealed_values_path)],
466
+ extra_args=["-f", str(vault_values_path)],
210
467
  )
211
468
 
212
469
  if not ctx.dry_run:
213
- _wait_for_sealed_secrets_ready(ctx, sealed_ns)
214
-
215
- typer.echo(
216
- "Para criar sealed-secrets a partir do seu desktop, exporte o certificado publico e use kubeseal."
217
- )
218
- if typer.confirm("Exportar certificado publico agora?", default=True):
219
- _export_sealed_cert(sealed_ns, ctx)
470
+ _wait_for_pods_ready(ctx, vault_ns, "app.kubernetes.io/name=vault", timeout=180)
471
+
472
+ # Inicializa Vault
473
+ root_token, unseal_keys = _initialize_vault(ctx, vault_ns, node_ip)
474
+
475
+ # Destrava Vault
476
+ _unseal_vault(ctx, vault_ns, unseal_keys)
477
+
478
+ # Configura Vault
479
+ _enable_kv_secrets(ctx, vault_ns, root_token)
480
+ _configure_kubernetes_auth(ctx, vault_ns, root_token)
220
481
 
221
- # external-secrets
482
+ # ========== External Secrets Operator ==========
222
483
  typer.secho("\n== External Secrets Operator ==", fg=typer.colors.CYAN, bold=True)
223
484
 
224
- # Prompt opcional de limpeza
225
485
  if _check_existing_external_secrets(ctx, eso_ns):
226
486
  cleanup = typer.confirm(
227
487
  "Instalacao anterior do External Secrets detectada. Limpar antes de reinstalar?",
@@ -282,12 +542,48 @@ resources:
282
542
  extra_args=["-f", str(eso_values_path)],
283
543
  )
284
544
 
285
- typer.secho("\n✓ Secrets management instalado com sucesso.", fg=typer.colors.GREEN, bold=True)
286
- typer.echo(
287
- "\nExternal Secrets Operator instalado. Configure um SecretStore/ClusterSecretStore conforme seu provedor (AWS/GCP/Vault)."
288
- )
545
+ if not ctx.dry_run:
546
+ _wait_for_pods_ready(ctx, eso_ns, "app.kubernetes.io/name=external-secrets", timeout=120)
547
+
548
+ # Configura integração Vault + ESO
549
+ _create_eso_policy_and_role(ctx, vault_ns, root_token, eso_ns)
550
+ _create_secretstore_example(ctx, vault_ns, eso_ns, node_ip)
551
+ _create_example_secret(ctx, vault_ns, root_token)
289
552
 
290
- typer.secho("\nDicas rapidas:", fg=typer.colors.GREEN)
291
- typer.echo(f"- Gere sealed-secrets localmente: kubeseal --controller-namespace {sealed_ns} --controller-name sealed-secrets < secret.yaml > sealed.yaml")
292
- typer.echo("- Para ESO: crie um SecretStore apontando para seu backend e um ExternalSecret referenciando os keys.")
553
+ typer.secho("\n✓ Vault + External Secrets Operator instalado com sucesso!", fg=typer.colors.GREEN, bold=True)
554
+
555
+ typer.secho("\n=== Acesso ao Vault UI ===", fg=typer.colors.CYAN)
556
+ typer.echo(f"URL: http://{node_ip}:30820")
557
+ typer.echo(f"Token: {root_token if not ctx.dry_run else '<root-token>'}")
558
+
559
+ typer.secho("\n=== Como usar ===", fg=typer.colors.CYAN)
560
+ typer.echo("1. Criar segredo no Vault:")
561
+ typer.echo(f" kubectl -n {vault_ns} exec vault-0 -- vault kv put secret/myapp username=admin password=secret123")
562
+
563
+ typer.echo("\n2. Criar ExternalSecret:")
564
+ typer.echo(" kubectl apply -f - <<EOF")
565
+ typer.echo(" apiVersion: external-secrets.io/v1beta1")
566
+ typer.echo(" kind: ExternalSecret")
567
+ typer.echo(" metadata:")
568
+ typer.echo(" name: myapp-secret")
569
+ typer.echo(" spec:")
570
+ typer.echo(" secretStoreRef:")
571
+ typer.echo(" name: vault-backend")
572
+ typer.echo(" kind: ClusterSecretStore")
573
+ typer.echo(" target:")
574
+ typer.echo(" name: myapp-secret")
575
+ typer.echo(" data:")
576
+ typer.echo(" - secretKey: username")
577
+ typer.echo(" remoteRef:")
578
+ typer.echo(" key: secret/myapp")
579
+ typer.echo(" property: username")
580
+ typer.echo(" EOF")
581
+
582
+ typer.echo("\n3. Secret será sincronizado automaticamente!")
583
+ typer.echo(" kubectl get secret myapp-secret -o yaml")
584
+
585
+ typer.secho("\n⚠️ IMPORTANTE:", fg=typer.colors.YELLOW, bold=True)
586
+ typer.echo(f"- Root token e unseal keys salvos em: /etc/vault/keys.json")
587
+ typer.echo("- Faça backup dessas keys em local seguro!")
588
+ typer.echo("- Após reboot do Vault, use: kubectl -n vault exec vault-0 -- vault operator unseal")
293
589
 
@@ -1,10 +1,46 @@
1
1
  """Backup e restore com Velero (production-ready)."""
2
2
 
3
3
  import time
4
+ from pathlib import Path
4
5
 
5
6
  import typer
6
7
 
7
8
  from raijin_server.utils import ExecutionContext, ensure_tool, require_root, run_cmd
9
+ from raijin_server.minio_utils import get_or_create_minio_user
10
+
11
+
12
+ def _create_velero_credentials_file(ctx: ExecutionContext) -> str:
13
+ """Cria arquivo de credenciais para Velero com usuário MinIO dedicado.
14
+
15
+ Returns:
16
+ Caminho para o arquivo de credenciais
17
+ """
18
+ typer.echo("\nConfigurando credenciais MinIO para Velero...")
19
+
20
+ # Cria usuário MinIO específico para Velero
21
+ access_key, secret_key = get_or_create_minio_user(
22
+ ctx=ctx,
23
+ app_name="velero",
24
+ buckets=["velero-backups"],
25
+ namespace="velero",
26
+ )
27
+
28
+ # Cria arquivo de credenciais no formato esperado pelo Velero
29
+ credentials_path = Path("/etc/velero/credentials")
30
+ credentials_path.parent.mkdir(parents=True, exist_ok=True)
31
+
32
+ credentials_content = f"""[default]
33
+ aws_access_key_id = {access_key}
34
+ aws_secret_access_key = {secret_key}
35
+ """
36
+
37
+ if not ctx.dry_run:
38
+ credentials_path.write_text(credentials_content)
39
+ # Protege o arquivo
40
+ credentials_path.chmod(0o600)
41
+ typer.secho(f" ✓ Credenciais salvas em {credentials_path}", fg=typer.colors.GREEN)
42
+
43
+ return str(credentials_path)
8
44
 
9
45
 
10
46
  def _check_existing_velero(ctx: ExecutionContext) -> bool:
@@ -91,8 +127,19 @@ def run(ctx: ExecutionContext) -> None:
91
127
  provider = typer.prompt("Provider (aws, azure, gcp)", default="aws")
92
128
  bucket = typer.prompt("Bucket para backups", default="velero-backups")
93
129
  region = typer.prompt("Region", default="us-east-1")
94
- s3_url = typer.prompt("S3 URL (para MinIO usar http://minio.minio.svc:9000)", default="https://s3.amazonaws.com")
95
- secret_file = typer.prompt("Arquivo de credenciais (secret-file)", default="/etc/velero/credentials")
130
+ s3_url = typer.prompt("S3 URL (para MinIO usar http://minio.minio.svc:9000)", default="http://minio.minio.svc:9000")
131
+
132
+ # Cria credenciais automaticamente usando usuário MinIO dedicado
133
+ use_auto_credentials = typer.confirm(
134
+ "Criar credenciais MinIO automaticamente (usuário dedicado)?",
135
+ default=True,
136
+ )
137
+
138
+ if use_auto_credentials:
139
+ secret_file = _create_velero_credentials_file(ctx)
140
+ else:
141
+ secret_file = typer.prompt("Arquivo de credenciais (secret-file)", default="/etc/velero/credentials")
142
+
96
143
  use_restic = typer.confirm("Habilitar Restic/Kopia para backups de PV?", default=True)
97
144
  schedule = typer.prompt("Schedule cron para backups (ex: '0 2 * * *')", default="0 2 * * *")
98
145
 
@@ -34,7 +34,7 @@ MODULE_DEPENDENCIES = {
34
34
  "kafka": ["kubernetes"],
35
35
  "observability_ingress": ["traefik", "prometheus", "grafana"],
36
36
  "observability_dashboards": ["prometheus", "grafana"],
37
- "apokolips_demo": ["kubernetes", "traefik"],
37
+
38
38
  }
39
39
 
40
40
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: raijin-server
3
- Version: 0.3.4
3
+ Version: 0.3.7
4
4
  Summary: CLI para automacao de setup e hardening de servidores Ubuntu Server.
5
5
  Home-page: https://example.com/raijin-server
6
6
  Author: Equipe Raijin