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
|
@@ -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
|
|
@@ -15,6 +15,7 @@ __all__ = [
|
|
|
15
15
|
"prometheus",
|
|
16
16
|
"grafana",
|
|
17
17
|
"loki",
|
|
18
|
+
"harbor",
|
|
18
19
|
"harness",
|
|
19
20
|
"velero",
|
|
20
21
|
"kafka",
|
|
@@ -23,15 +24,12 @@ __all__ = [
|
|
|
23
24
|
"vpn",
|
|
24
25
|
"vpn_client",
|
|
25
26
|
"internal_dns",
|
|
26
|
-
"observability_ingress",
|
|
27
|
-
"observability_dashboards",
|
|
28
|
-
"apokolips_demo",
|
|
29
27
|
"cert_manager",
|
|
30
28
|
"secrets",
|
|
31
29
|
"full_install",
|
|
32
30
|
]
|
|
33
31
|
|
|
34
|
-
from raijin_server.modules import calico, essentials, firewall, grafana, harness, hardening, istio
|
|
35
|
-
from raijin_server.modules import kafka, kong, kubernetes, loki, minio, network
|
|
36
|
-
from raijin_server.modules import
|
|
32
|
+
from raijin_server.modules import calico, essentials, firewall, grafana, harbor, harness, hardening, istio
|
|
33
|
+
from raijin_server.modules import kafka, kong, kubernetes, loki, minio, network
|
|
34
|
+
from raijin_server.modules import prometheus, traefik, velero, secrets, cert_manager
|
|
37
35
|
from raijin_server.modules import bootstrap, full_install, sanitize, ssh_hardening, vpn, vpn_client, internal_dns
|
|
@@ -15,12 +15,11 @@ from raijin_server.modules import (
|
|
|
15
15
|
essentials,
|
|
16
16
|
firewall,
|
|
17
17
|
grafana,
|
|
18
|
+
harbor,
|
|
18
19
|
hardening,
|
|
19
20
|
kubernetes,
|
|
20
21
|
loki,
|
|
21
22
|
network,
|
|
22
|
-
observability_dashboards,
|
|
23
|
-
observability_ingress,
|
|
24
23
|
prometheus,
|
|
25
24
|
secrets,
|
|
26
25
|
sanitize,
|
|
@@ -171,6 +170,13 @@ def _diag_secrets(ctx: ExecutionContext) -> None:
|
|
|
171
170
|
_diag_namespace("external-secrets", ctx)
|
|
172
171
|
|
|
173
172
|
|
|
173
|
+
def _diag_harbor(ctx: ExecutionContext) -> None:
|
|
174
|
+
"""Diagnostico do namespace harbor."""
|
|
175
|
+
ns = "harbor"
|
|
176
|
+
_run_cmd("Harbor pods", ["kubectl", "get", "pods", "-n", ns, "-o", "wide"], ctx)
|
|
177
|
+
_diag_namespace(ns, ctx)
|
|
178
|
+
|
|
179
|
+
|
|
174
180
|
def _diag_prometheus(ctx: ExecutionContext) -> None:
|
|
175
181
|
ns = "observability"
|
|
176
182
|
_run_cmd("Prometheus pods", ["kubectl", "get", "pods", "-n", ns, "-l", "app.kubernetes.io/name=prometheus"], ctx)
|
|
@@ -195,18 +201,6 @@ def _diag_traefik(ctx: ExecutionContext) -> None:
|
|
|
195
201
|
_diag_namespace(ns, ctx)
|
|
196
202
|
|
|
197
203
|
|
|
198
|
-
def _diag_observability_ingress(ctx: ExecutionContext) -> None:
|
|
199
|
-
ns = "observability"
|
|
200
|
-
_run_cmd("Ingress objects", ["kubectl", "get", "ingress", "-n", ns], ctx)
|
|
201
|
-
_diag_namespace(ns, ctx)
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
def _diag_observability_dashboards(ctx: ExecutionContext) -> None:
|
|
205
|
-
ns = "observability"
|
|
206
|
-
_run_cmd("ConfigMaps dashboards", ["kubectl", "get", "configmap", "-n", ns, "-l", "raijin/dashboards=true"], ctx)
|
|
207
|
-
_diag_namespace(ns, ctx)
|
|
208
|
-
|
|
209
|
-
|
|
210
204
|
def _diag_minio(ctx: ExecutionContext) -> None:
|
|
211
205
|
ns = "minio"
|
|
212
206
|
_diag_namespace(ns, ctx)
|
|
@@ -232,12 +226,11 @@ DIAG_HANDLERS = {
|
|
|
232
226
|
"cert_manager": cert_manager.diagnose,
|
|
233
227
|
"calico": _diag_calico,
|
|
234
228
|
"secrets": _diag_secrets,
|
|
229
|
+
"harbor": _diag_harbor,
|
|
235
230
|
"prometheus": _diag_prometheus,
|
|
236
231
|
"grafana": _diag_grafana,
|
|
237
232
|
"loki": _diag_loki,
|
|
238
233
|
"traefik": _diag_traefik,
|
|
239
|
-
"observability_ingress": _diag_observability_ingress,
|
|
240
|
-
"observability_dashboards": _diag_observability_dashboards,
|
|
241
234
|
"minio": _diag_minio,
|
|
242
235
|
"kafka": _diag_kafka,
|
|
243
236
|
"velero": _diag_velero,
|
|
@@ -273,13 +266,12 @@ INSTALL_SEQUENCE = [
|
|
|
273
266
|
("kubernetes", kubernetes.run, "Cluster Kubernetes (kubeadm)", None),
|
|
274
267
|
("calico", calico.run, "CNI Calico + NetworkPolicy", None),
|
|
275
268
|
("cert_manager", _cert_manager_install_only, "cert-manager (instalacao base)", None),
|
|
276
|
-
("secrets", secrets.run, "
|
|
269
|
+
("secrets", secrets.run, "HashiCorp Vault + External Secrets Operator", None),
|
|
270
|
+
("harbor", harbor.run, "Container Registry com Vulnerability Scanning", None),
|
|
277
271
|
("prometheus", prometheus.run, "Monitoramento Prometheus", None),
|
|
278
272
|
("grafana", grafana.run, "Dashboards Grafana", None),
|
|
279
273
|
("loki", loki.run, "Logs centralizados Loki", None),
|
|
280
274
|
("traefik", traefik.run, "Ingress Controller Traefik", None),
|
|
281
|
-
("observability_ingress", observability_ingress.run, "Ingress seguro para Grafana/Prometheus/Alertmanager", None),
|
|
282
|
-
("observability_dashboards", observability_dashboards.run, "Dashboards opinativos e alertas", None),
|
|
283
275
|
]
|
|
284
276
|
|
|
285
277
|
|