raijin-server 0.2.41__py3-none-any.whl → 0.3.1__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.

@@ -0,0 +1,446 @@
1
+ """Configuração de DNS interno para domínios privados (*.asgard.internal)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import textwrap
6
+ from pathlib import Path
7
+
8
+ import typer
9
+
10
+ from raijin_server.utils import (
11
+ ExecutionContext,
12
+ kubectl_apply,
13
+ logger,
14
+ require_root,
15
+ run_cmd,
16
+ write_file,
17
+ )
18
+
19
+ NAMESPACE = "kube-system"
20
+ MANIFEST_PATH = Path("/tmp/raijin-internal-dns.yaml")
21
+ INGRESS_MANIFEST_PATH = Path("/tmp/raijin-internal-ingress.yaml")
22
+
23
+
24
+ def _get_node_ip(ctx: ExecutionContext) -> str:
25
+ """Obtém o IP do nó do cluster."""
26
+ result = run_cmd(
27
+ [
28
+ "kubectl",
29
+ "get",
30
+ "nodes",
31
+ "-o",
32
+ "jsonpath={.items[0].status.addresses[?(@.type=='InternalIP')].address}",
33
+ ],
34
+ ctx,
35
+ check=False,
36
+ )
37
+
38
+ if result.returncode == 0 and result.stdout.strip():
39
+ return result.stdout.strip()
40
+
41
+ return "10.8.0.1" # Fallback para IP da VPN
42
+
43
+
44
+ def _get_current_corefile(ctx: ExecutionContext) -> str:
45
+ """Obtém o Corefile atual do CoreDNS."""
46
+ result = run_cmd(
47
+ [
48
+ "kubectl", "get", "configmap", "coredns", "-n", NAMESPACE,
49
+ "-o", "jsonpath={.data.Corefile}",
50
+ ],
51
+ ctx,
52
+ check=False,
53
+ )
54
+
55
+ if result.returncode == 0:
56
+ return result.stdout.strip()
57
+
58
+ return ""
59
+
60
+
61
+ def _build_coredns_patch(domain: str, node_ip: str, current_corefile: str) -> str:
62
+ """Cria patch para adicionar zona customizada ao Corefile existente."""
63
+ # Bloco de configuração para o domínio interno
64
+ internal_zone = f"""
65
+ {domain}:53 {{
66
+ errors
67
+ cache 30
68
+ hosts {{
69
+ {node_ip} grafana.{domain}
70
+ {node_ip} prometheus.{domain}
71
+ {node_ip} alertmanager.{domain}
72
+ {node_ip} loki.{domain}
73
+ {node_ip} minio.{domain}
74
+ {node_ip} traefik.{domain}
75
+ {node_ip} kong.{domain}
76
+ fallthrough
77
+ }}
78
+ }}
79
+ """
80
+
81
+ # Se já tem configuração para o domínio, não adiciona duplicado
82
+ if f"{domain}:53" in current_corefile:
83
+ return ""
84
+
85
+ # Adiciona a nova zona no início do Corefile
86
+ new_corefile = internal_zone + "\n" + current_corefile
87
+
88
+ return new_corefile
89
+
90
+
91
+ def _build_coredns_configmap(domain: str, node_ip: str, current_corefile: str) -> str:
92
+ """Cria ConfigMap do CoreDNS atualizado com zona customizada."""
93
+ new_corefile = _build_coredns_patch(domain, node_ip, current_corefile)
94
+
95
+ if not new_corefile:
96
+ return ""
97
+
98
+ # Escapa caracteres especiais para JSON
99
+ escaped_corefile = new_corefile.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n")
100
+
101
+ return textwrap.dedent(
102
+ f"""
103
+ apiVersion: v1
104
+ kind: ConfigMap
105
+ metadata:
106
+ name: coredns
107
+ namespace: {NAMESPACE}
108
+ data:
109
+ Corefile: |
110
+ {textwrap.indent(new_corefile, ' ')}
111
+ """
112
+ ).strip()
113
+
114
+
115
+ def _build_internal_ingress(domain: str, services: list[dict]) -> str:
116
+ """Cria Ingress interno para os serviços administrativos."""
117
+ ingress_rules = []
118
+
119
+ for svc in services:
120
+ name = svc["name"]
121
+ namespace = svc["namespace"]
122
+ port = svc["port"]
123
+ host = svc["host"]
124
+
125
+ ingress_rules.append(
126
+ textwrap.dedent(
127
+ f"""
128
+ ---
129
+ apiVersion: networking.k8s.io/v1
130
+ kind: Ingress
131
+ metadata:
132
+ name: {name}-internal
133
+ namespace: {namespace}
134
+ annotations:
135
+ traefik.ingress.kubernetes.io/router.entrypoints: web
136
+ traefik.ingress.kubernetes.io/router.priority: "10"
137
+ spec:
138
+ ingressClassName: traefik
139
+ rules:
140
+ - host: {host}.{domain}
141
+ http:
142
+ paths:
143
+ - path: /
144
+ pathType: Prefix
145
+ backend:
146
+ service:
147
+ name: {name}
148
+ port:
149
+ number: {port}
150
+ """
151
+ ).strip()
152
+ )
153
+
154
+ return "\n".join(ingress_rules)
155
+
156
+
157
+ def _detect_services(ctx: ExecutionContext) -> list[dict]:
158
+ """Detecta serviços instalados que podem ter Ingress interno."""
159
+ services = [
160
+ {
161
+ "name": "grafana",
162
+ "namespace": "observability",
163
+ "port": 80,
164
+ "host": "grafana",
165
+ "label": "Grafana"
166
+ },
167
+ {
168
+ "name": "kube-prometheus-stack-prometheus",
169
+ "namespace": "observability",
170
+ "port": 9090,
171
+ "host": "prometheus",
172
+ "label": "Prometheus"
173
+ },
174
+ {
175
+ "name": "kube-prometheus-stack-alertmanager",
176
+ "namespace": "observability",
177
+ "port": 9093,
178
+ "host": "alertmanager",
179
+ "label": "Alertmanager"
180
+ },
181
+ {
182
+ "name": "loki",
183
+ "namespace": "observability",
184
+ "port": 3100,
185
+ "host": "loki",
186
+ "label": "Loki"
187
+ },
188
+ {
189
+ "name": "minio-console",
190
+ "namespace": "minio",
191
+ "port": 9001,
192
+ "host": "minio",
193
+ "label": "MinIO Console"
194
+ },
195
+ {
196
+ "name": "traefik",
197
+ "namespace": "traefik",
198
+ "port": 9000,
199
+ "host": "traefik",
200
+ "label": "Traefik Dashboard"
201
+ },
202
+ {
203
+ "name": "kong-admin",
204
+ "namespace": "kong",
205
+ "port": 8001,
206
+ "host": "kong",
207
+ "label": "Kong Admin API"
208
+ },
209
+ ]
210
+
211
+ available = []
212
+ for svc in services:
213
+ result = run_cmd(
214
+ ["kubectl", "get", "svc", svc["name"], "-n", svc["namespace"]],
215
+ ctx,
216
+ check=False,
217
+ )
218
+ if result.returncode == 0:
219
+ available.append(svc)
220
+
221
+ return available
222
+
223
+
224
+ def _update_vpn_dns(domain: str, node_ip: str, ctx: ExecutionContext) -> None:
225
+ """Atualiza configuração do WireGuard para usar DNS interno."""
226
+ wg_conf = Path("/etc/wireguard/wg0.conf")
227
+
228
+ if not wg_conf.exists():
229
+ typer.secho(
230
+ "⚠ Arquivo /etc/wireguard/wg0.conf não encontrado.",
231
+ fg=typer.colors.YELLOW,
232
+ )
233
+ typer.echo("Configure a VPN primeiro com: sudo raijin vpn")
234
+ return
235
+
236
+ if ctx.dry_run:
237
+ typer.echo(f"[dry-run] Atualizaria DNS no WireGuard para {node_ip}")
238
+ return
239
+
240
+ content = wg_conf.read_text()
241
+
242
+ # Procura pela linha DNS na seção [Interface]
243
+ lines = content.split("\n")
244
+ updated = False
245
+
246
+ for i, line in enumerate(lines):
247
+ if line.strip().startswith("DNS ="):
248
+ # Atualiza para usar o DNS do cluster
249
+ lines[i] = f"DNS = {node_ip}"
250
+ updated = True
251
+ break
252
+
253
+ if updated:
254
+ wg_conf.write_text("\n".join(lines))
255
+ logger.info("DNS do WireGuard atualizado para %s", node_ip)
256
+
257
+ # Atualiza clientes existentes
258
+ clients_dir = Path("/etc/wireguard/clients")
259
+ if clients_dir.exists():
260
+ for client_conf in clients_dir.glob("*.conf"):
261
+ client_content = client_conf.read_text()
262
+ client_lines = client_content.split("\n")
263
+
264
+ for i, line in enumerate(client_lines):
265
+ if line.strip().startswith("DNS ="):
266
+ client_lines[i] = f"DNS = {node_ip}"
267
+ break
268
+
269
+ client_conf.write_text("\n".join(client_lines))
270
+ logger.info("DNS atualizado no cliente %s", client_conf.name)
271
+
272
+
273
+ def run(ctx: ExecutionContext) -> None:
274
+ """Configura DNS interno para domínios privados."""
275
+ require_root(ctx)
276
+
277
+ typer.secho("\n🌐 Configuração de DNS Interno", fg=typer.colors.CYAN, bold=True)
278
+ typer.echo("\nEsta ferramenta configura DNS interno para acessar serviços via")
279
+ typer.echo("domínios amigáveis como grafana.asgard.internal")
280
+
281
+ typer.secho("\n⚠ Importante:", fg=typer.colors.YELLOW, bold=True)
282
+ typer.echo("- Use extensões reservadas para redes privadas (.internal, .home.arpa)")
283
+ typer.echo("- Evite TLDs reais como .io, .com, .net, etc.")
284
+ typer.echo("- Recomendado: .internal (RFC 6762)")
285
+
286
+ domain = typer.prompt(
287
+ "\nDomínio base (sem o ponto inicial)",
288
+ default="asgard.internal",
289
+ )
290
+
291
+ if "." not in domain:
292
+ typer.secho("⚠ Use um domínio com extensão, ex: asgard.internal", fg=typer.colors.YELLOW)
293
+ if not typer.confirm("Continuar mesmo assim?", default=False):
294
+ raise typer.Exit(0)
295
+
296
+ # Valida extensões não recomendadas
297
+ if any(domain.endswith(ext) for ext in [".io", ".com", ".net", ".org", ".dev"]):
298
+ typer.secho(
299
+ f"⚠ Extensão '{domain.split('.')[-1]}' é um TLD real, não recomendado para uso interno!",
300
+ fg=typer.colors.RED,
301
+ bold=True,
302
+ )
303
+ typer.echo("Recomendação: use .internal, .local, ou .home.arpa")
304
+ if not typer.confirm("Continuar mesmo assim?", default=False):
305
+ raise typer.Exit(0)
306
+
307
+ node_ip = _get_node_ip(ctx)
308
+ typer.echo(f"\nIP do nó detectado: {node_ip}")
309
+
310
+ custom_ip = typer.prompt(
311
+ "IP para resolver os domínios (ENTER para usar o detectado)",
312
+ default=node_ip,
313
+ )
314
+
315
+ if custom_ip != node_ip:
316
+ node_ip = custom_ip
317
+
318
+ # 1. Configura CoreDNS
319
+ typer.secho("\n1️⃣ Configurando CoreDNS...", fg=typer.colors.CYAN)
320
+
321
+ # Obtém Corefile atual
322
+ current_corefile = _get_current_corefile(ctx)
323
+
324
+ if not current_corefile:
325
+ typer.secho(
326
+ "⚠ Não foi possível obter o Corefile atual do CoreDNS.",
327
+ fg=typer.colors.YELLOW,
328
+ )
329
+ typer.echo("Verifique se o cluster está acessível.")
330
+ raise typer.Exit(1)
331
+
332
+ # Verifica se já está configurado
333
+ if f"{domain}:53" in current_corefile:
334
+ typer.secho(
335
+ f"✓ Domínio {domain} já está configurado no CoreDNS.",
336
+ fg=typer.colors.GREEN,
337
+ )
338
+ typer.echo("Pulando configuração do CoreDNS...")
339
+ else:
340
+ coredns_cm = _build_coredns_configmap(domain, node_ip, current_corefile)
341
+
342
+ if not coredns_cm:
343
+ typer.secho("⚠ Não foi possível gerar a configuração do CoreDNS.", fg=typer.colors.YELLOW)
344
+ raise typer.Exit(1)
345
+
346
+ # Mostra preview antes de aplicar
347
+ if not ctx.dry_run:
348
+ typer.secho("\n📄 Preview da nova configuração do CoreDNS:", fg=typer.colors.YELLOW)
349
+ typer.echo("─" * 60)
350
+ # Mostra apenas a zona adicionada (não o Corefile inteiro)
351
+ typer.echo(f"Nova zona para {domain}:")
352
+ typer.echo(f" - grafana.{domain} → {node_ip}")
353
+ typer.echo(f" - prometheus.{domain} → {node_ip}")
354
+ typer.echo(f" - alertmanager.{domain} → {node_ip}")
355
+ typer.echo(f" - loki.{domain} → {node_ip}")
356
+ typer.echo(f" - minio.{domain} → {node_ip}")
357
+ typer.echo(f" - traefik.{domain} → {node_ip}")
358
+ typer.echo(f" - kong.{domain} → {node_ip}")
359
+ typer.echo("─" * 60)
360
+
361
+ typer.secho("\nℹ️ Esta alteração:", fg=typer.colors.CYAN)
362
+ typer.echo(" ✓ Adiciona zona DNS para *." + domain)
363
+ typer.echo(" ✓ Mantém todas as configurações existentes do CoreDNS")
364
+ typer.echo(" ✓ Resolve domínios internos para " + node_ip)
365
+
366
+ if not typer.confirm("\nAplicar configuração do CoreDNS?", default=True):
367
+ typer.secho("⏭️ Pulando configuração do CoreDNS", fg=typer.colors.YELLOW)
368
+ raise typer.Exit(0)
369
+
370
+ write_file(MANIFEST_PATH, coredns_cm, ctx)
371
+ kubectl_apply(str(MANIFEST_PATH), ctx)
372
+
373
+ # Reinicia CoreDNS
374
+ typer.echo("Reiniciando CoreDNS...")
375
+ run_cmd(
376
+ ["kubectl", "rollout", "restart", "deployment/coredns", "-n", NAMESPACE],
377
+ ctx,
378
+ )
379
+
380
+ # 2. Detecta serviços disponíveis
381
+ typer.secho("\n2️⃣ Detectando serviços...", fg=typer.colors.CYAN)
382
+ services = _detect_services(ctx)
383
+
384
+ if not services:
385
+ typer.secho(
386
+ "⚠ Nenhum serviço administrativo encontrado.",
387
+ fg=typer.colors.YELLOW,
388
+ )
389
+ typer.echo("Instale Grafana, Prometheus, MinIO, etc. primeiro.")
390
+ else:
391
+ typer.echo(f"\nEncontrados {len(services)} serviços:")
392
+ for svc in services:
393
+ typer.echo(f" ✓ {svc['label']}: {svc['host']}.{domain}")
394
+
395
+ if typer.confirm("\nCriar Ingress interno para esses serviços?", default=True):
396
+ typer.secho("\n3️⃣ Criando Ingress interno...", fg=typer.colors.CYAN)
397
+
398
+ ingress_manifest = _build_internal_ingress(domain, services)
399
+
400
+ # Preview dos Ingress
401
+ if not ctx.dry_run:
402
+ typer.secho("\n📄 Preview dos Ingress (primeiros 30 linhas):", fg=typer.colors.YELLOW)
403
+ typer.echo("─" * 60)
404
+ lines = ingress_manifest.split("\n")
405
+ typer.echo("\n".join(lines[:30]))
406
+ if len(lines) > 30:
407
+ typer.echo(f"... ({len(lines) - 30} linhas restantes)")
408
+ typer.echo("─" * 60)
409
+
410
+ typer.secho("\nℹ️ Esses Ingress:", fg=typer.colors.CYAN)
411
+ typer.echo(" ✓ Têm sufixo '-internal' no nome")
412
+ typer.echo(" ✓ Não alteram Ingress existentes")
413
+ typer.echo(" ✓ Roteiam por hostname via Traefik")
414
+
415
+ if not typer.confirm("\nAplicar Ingress internos?", default=True):
416
+ typer.secho("⏭️ Pulando criação de Ingress", fg=typer.colors.YELLOW)
417
+ return
418
+
419
+ write_file(INGRESS_MANIFEST_PATH, ingress_manifest, ctx)
420
+ kubectl_apply(str(INGRESS_MANIFEST_PATH), ctx)
421
+
422
+ # 3. Atualiza VPN
423
+ if typer.confirm("\n4️⃣ Atualizar DNS do WireGuard?", default=True):
424
+ _update_vpn_dns(domain, node_ip, ctx)
425
+
426
+ typer.secho("\n⚠ Ação necessária:", fg=typer.colors.YELLOW, bold=True)
427
+ typer.echo("1. Reinicie o WireGuard no servidor:")
428
+ typer.echo(" sudo wg-quick down wg0 && sudo wg-quick up wg0")
429
+ typer.echo("\n2. Distribua novos arquivos .conf aos clientes:")
430
+ typer.echo(" sudo ls /etc/wireguard/clients/")
431
+ typer.echo("\n3. Clientes devem reconectar ao VPN para usar o novo DNS")
432
+
433
+ typer.secho("\n✓ DNS interno configurado com sucesso!", fg=typer.colors.GREEN, bold=True)
434
+
435
+ if services:
436
+ typer.secho("\n📋 Acesso aos serviços:", fg=typer.colors.CYAN, bold=True)
437
+ typer.echo("\nDepois de conectar à VPN, acesse:")
438
+ for svc in services:
439
+ typer.echo(f" • {svc['label']}: http://{svc['host']}.{domain}")
440
+
441
+ typer.secho("\n💡 Dica:", fg=typer.colors.CYAN)
442
+ typer.echo("Você não precisa mais de port-forward!")
443
+ typer.echo("Basta conectar à VPN e acessar diretamente os domínios.")
444
+
445
+ typer.secho("\n🔍 Testar resolução DNS:", fg=typer.colors.CYAN)
446
+ typer.echo(f"kubectl run -it --rm dns-test --image=busybox --restart=Never -- nslookup grafana.{domain}")
@@ -393,16 +393,20 @@ podAnnotations:
393
393
  # Mostra informacoes uteis
394
394
  typer.secho("\n✓ Kong Gateway instalado com sucesso.", fg=typer.colors.GREEN, bold=True)
395
395
 
396
- typer.echo("\n📌 Acesso ao Kong Proxy:")
396
+ typer.echo("\n📌 Acesso ao Kong Proxy (APIs públicas):")
397
397
  if service_type == "LoadBalancer":
398
398
  typer.echo(" kubectl -n kong get svc kong-kong-proxy # Aguarde EXTERNAL-IP")
399
399
  else:
400
400
  typer.echo(" kubectl -n kong get svc kong-kong-proxy # Use NodePort")
401
401
 
402
402
  if enable_admin:
403
- typer.echo("\n📌 Admin API (port-forward):")
404
- typer.echo(" kubectl -n kong port-forward svc/kong-kong-admin 8001:8001")
405
- typer.echo(" curl http://localhost:8001/status")
403
+ typer.secho("\n🔒 Admin API - Acesso Seguro via VPN:", fg=typer.colors.CYAN, bold=True)
404
+ typer.echo("\n1. Configure VPN: sudo raijin vpn")
405
+ typer.echo("2. Conecte via WireGuard")
406
+ typer.echo("3. Port-forward:")
407
+ typer.echo(" kubectl -n kong port-forward svc/kong-kong-admin 8001:8001")
408
+ typer.echo("4. Teste:")
409
+ typer.echo(" curl http://localhost:8001/status")
406
410
 
407
411
  if enable_metrics:
408
412
  typer.echo("\n📌 Métricas Prometheus:")
@@ -243,6 +243,9 @@ def _set_default_storage_class(ctx: ExecutionContext, name: str) -> None:
243
243
 
244
244
  def _ensure_storage_class(ctx: ExecutionContext) -> str:
245
245
  """Garante que existe uma StorageClass disponivel, instalando local-path se necessario."""
246
+ if ctx.dry_run:
247
+ return "local-path" # Retorna um valor dummy para dry-run
248
+
246
249
  default_sc = _get_default_storage_class(ctx)
247
250
  available = _list_storage_classes(ctx)
248
251
 
@@ -509,9 +512,16 @@ def run(ctx: ExecutionContext) -> None:
509
512
  typer.echo("\nCredenciais:")
510
513
  typer.echo(f" Root User: {root_user}")
511
514
  typer.echo(f" Root Password: {root_password}")
512
- typer.echo("\nPara acessar a API (port-forward):")
513
- typer.echo(" kubectl -n minio port-forward svc/minio 9000:9000")
515
+
514
516
  if enable_console:
515
- typer.echo("\nPara acessar o Console Web (port-forward):")
516
- typer.echo(" kubectl -n minio port-forward svc/minio-console 9001:9001")
517
- typer.echo(" Acesse: http://localhost:9001")
517
+ typer.secho("\n🔒 Acesso Seguro ao MinIO Console via VPN:", fg=typer.colors.CYAN, bold=True)
518
+ typer.echo("\n1. Configure VPN (se ainda não tiver):")
519
+ typer.echo(" sudo raijin vpn")
520
+ typer.echo("\n2. Conecte via WireGuard")
521
+ typer.echo("\n3. Faça port-forward:")
522
+ typer.echo(" kubectl -n minio port-forward svc/minio-console 9001:9001")
523
+ typer.echo("\n4. Acesse no navegador:")
524
+ typer.echo(" http://localhost:9001")
525
+
526
+ typer.echo("\nPara acessar a API S3 (port-forward):")
527
+ typer.echo(" kubectl -n minio port-forward svc/minio 9000:9000")
@@ -163,7 +163,35 @@ spec:
163
163
 
164
164
  def run(ctx: ExecutionContext) -> None:
165
165
  require_root(ctx)
166
- typer.echo("Provisionando ingress seguro para observabilidade...")
166
+
167
+ typer.secho("⚠️ AVISO DE SEGURANÇA", fg=typer.colors.RED, bold=True)
168
+ typer.secho(
169
+ "\nExpor ferramentas de observabilidade publicamente é um RISCO DE SEGURANÇA significativo!",
170
+ fg=typer.colors.YELLOW,
171
+ )
172
+ typer.secho(
173
+ "Mesmo com TLS e BasicAuth, você estará expondo informações sensíveis do cluster.\n",
174
+ fg=typer.colors.YELLOW,
175
+ )
176
+ typer.secho("✅ RECOMENDAÇÃO FORTE:", fg=typer.colors.GREEN, bold=True)
177
+ typer.echo("1. Configure VPN: sudo raijin vpn")
178
+ typer.echo("2. Use port-forward via VPN para acesso seguro")
179
+ typer.echo("3. Mantenha os dashboards APENAS na rede interna\n")
180
+
181
+ proceed = typer.confirm(
182
+ "Você REALMENTE quer expor os dashboards publicamente?",
183
+ default=False
184
+ )
185
+
186
+ if not proceed:
187
+ typer.secho("\n✓ Boa decisão! Use VPN para acesso seguro.", fg=typer.colors.GREEN)
188
+ typer.echo("\nPara acessar via VPN + port-forward:")
189
+ typer.echo(" kubectl -n observability port-forward svc/grafana 3000:80")
190
+ typer.echo(" kubectl -n observability port-forward svc/kube-prometheus-stack-prometheus 9090:9090")
191
+ typer.echo(" kubectl -n observability port-forward svc/kube-prometheus-stack-alertmanager 9093:9093")
192
+ raise typer.Exit(0)
193
+
194
+ typer.echo("\nProvisionando ingress seguro para observabilidade...")
167
195
 
168
196
  namespace = typer.prompt("Namespace dos componentes", default="observability")
169
197
  ingress_class = typer.prompt("IngressClass dedicada", default="traefik")