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.
- raijin_server/__init__.py +1 -1
- raijin_server/cli.py +6 -25
- raijin_server/healthchecks.py +1 -55
- raijin_server/minio_utils.py +562 -0
- raijin_server/modules/__init__.py +4 -6
- raijin_server/modules/full_install.py +11 -19
- raijin_server/modules/harbor.py +669 -0
- raijin_server/modules/secrets.py +392 -96
- raijin_server/modules/velero.py +49 -2
- raijin_server/validators.py +1 -1
- {raijin_server-0.3.4.dist-info → raijin_server-0.3.7.dist-info}/METADATA +1 -1
- {raijin_server-0.3.4.dist-info → raijin_server-0.3.7.dist-info}/RECORD +16 -17
- raijin_server/modules/apokolips_demo.py +0 -414
- raijin_server/modules/observability_dashboards.py +0 -233
- raijin_server/modules/observability_ingress.py +0 -246
- {raijin_server-0.3.4.dist-info → raijin_server-0.3.7.dist-info}/WHEEL +0 -0
- {raijin_server-0.3.4.dist-info → raijin_server-0.3.7.dist-info}/entry_points.txt +0 -0
- {raijin_server-0.3.4.dist-info → raijin_server-0.3.7.dist-info}/licenses/LICENSE +0 -0
- {raijin_server-0.3.4.dist-info → raijin_server-0.3.7.dist-info}/top_level.txt +0 -0
raijin_server/modules/secrets.py
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
"""Automacao de
|
|
1
|
+
"""Automacao de HashiCorp Vault e External Secrets Operator (production-ready).
|
|
2
2
|
|
|
3
|
-
Instala
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
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
|
|
40
|
-
"""Verifica se existe instalacao do
|
|
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", "
|
|
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
|
|
60
|
-
"""Remove instalacao anterior do
|
|
61
|
-
typer.echo("Removendo instalacao anterior do
|
|
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", "
|
|
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
|
|
86
|
-
"""Aguarda pods
|
|
87
|
-
typer.echo("Aguardando pods
|
|
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",
|
|
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("
|
|
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
|
|
123
|
+
typer.secho(f" Timeout aguardando pods {label}.", fg=typer.colors.YELLOW)
|
|
118
124
|
return False
|
|
119
125
|
|
|
120
126
|
|
|
121
|
-
def
|
|
122
|
-
"""
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
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("
|
|
144
|
-
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
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
|
|
367
|
+
typer.echo("Instalando HashiCorp Vault + External Secrets Operator...")
|
|
163
368
|
|
|
164
|
-
|
|
165
|
-
eso_ns = typer.prompt("Namespace para
|
|
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
|
-
#
|
|
170
|
-
typer.secho("\n==
|
|
385
|
+
# ========== HashiCorp Vault ==========
|
|
386
|
+
typer.secho("\n== HashiCorp Vault ==", fg=typer.colors.CYAN, bold=True)
|
|
171
387
|
|
|
172
|
-
|
|
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
|
|
390
|
+
"Instalacao anterior do Vault detectada. Limpar antes de reinstalar?",
|
|
176
391
|
default=False,
|
|
177
392
|
)
|
|
178
393
|
if cleanup:
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
|
|
199
|
-
write_file(
|
|
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
|
-
"
|
|
203
|
-
"
|
|
204
|
-
|
|
459
|
+
"vault",
|
|
460
|
+
"vault",
|
|
461
|
+
vault_ns,
|
|
205
462
|
ctx,
|
|
206
|
-
repo="
|
|
207
|
-
repo_url="https://
|
|
463
|
+
repo="hashicorp",
|
|
464
|
+
repo_url="https://helm.releases.hashicorp.com",
|
|
208
465
|
create_namespace=True,
|
|
209
|
-
extra_args=["-f", str(
|
|
466
|
+
extra_args=["-f", str(vault_values_path)],
|
|
210
467
|
)
|
|
211
468
|
|
|
212
469
|
if not ctx.dry_run:
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
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
|
-
#
|
|
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
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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("\
|
|
291
|
-
|
|
292
|
-
typer.
|
|
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
|
|
raijin_server/modules/velero.py
CHANGED
|
@@ -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="
|
|
95
|
-
|
|
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
|
|
raijin_server/validators.py
CHANGED