Genomgång av flöden och infrastruktur — infrakarta i Terraform
Infrastrukturen får en definition som går att läsa: infra/terraform, där karta.tf beskriver hela systemet en gång som data — tjänster, portar, routing, vilken tjänst som ser vilken hemlighet, dataflöden och gränser. Resten av filerna läser därifrån i stället för att upprepa namn och portar, och `terraform output karta` skriver ut samma innehåll i klartext direkt ur definitionen. Filerna är numrerade i läsordning. Genomgången hittade sex saker som är åtgärdade här: Uppslaget mot märkesspecifika kopplingar kunde riktas inåt. Bas-URL:en sätts av kundens administratör men anropet görs av vår server — 169.254.169.254 eller ett internt tjänstenamn hade nått molnets metadatatjänst respektive klustrets insida, och svaret kommit tillbaka mappat genom svarsfälten. En tenant-administratör är inte infrastrukturens ägare. Nu stoppas IP-literaler, namn som resolvar till privata adresser och .local/.internal innan något anrop görs, och nätverkspolicyn undantar samma nät. TILLAT_INTERNA_UPPSLAG öppnar för verkstäder som har OEM-servern på eget nät. Delningsfiltret var en nekalista, alltså blev varje ny händelsetyp automatiskt synlig i kundens delningslänk tills någon kom ihåg att neka den — fel håll att fela åt på en integritetsgräns. Nu räknas i stället upp vad som får delas per nivå, och ett test kräver att varje händelsetyp i domänmodellen är klassificerad. Samma ändring i Supabase-funktionen via ny migration. Beteendet i dag är oförändrat; det är riktningen som vänts. Ingressen saknade kroppsgräns och hade därmed nginx standard på 1 MB medan tjänsten tar 4 MB — foto- och videodokumentation hade avvisats i produktion men aldrig i testerna. Satt till 8 MB i båda vägarna. Vidare: nätverkspolicyer som stänger namnrymden och bara öppnar de faktiska flödena, CORS-lista via TILLATNA_URSPRUNG i stället för "*", och säkerhetskontext + startprob på databaspodden. Kustomize-/Argo CD-vägen finns kvar men ska inte köras mot samma kluster som Terraform — selfHeal och prune motarbetar terraform apply. Val och bytesväg dokumenterade. Verifierat: 87 vitest-tester, typkontroll, eslint, OpenAPI-validering, terraform fmt och integrationstest mot riktig Postgres inklusive den nya spärren. terraform validate kunde inte köras här — registry.terraform.io är blockerad av sessionens egress-policy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
# Plattformstjänsten — systemets mitt.
|
||||
#
|
||||
# Äger: inloggning (bcrypt via pgcrypto, HS256-JWT med roll + organisation
|
||||
# i anspråken), append-only händelse-API, Live Share med serverstyrd
|
||||
# filtrering, organisationsinställningar, ECM-regelpaketet och de
|
||||
# märkesspecifika kopplingarna.
|
||||
#
|
||||
# Ser tre hemligheter och inga fler: JWT-hemligheten (delas med
|
||||
# orkestern), databaslösenordet och krypteringsnyckeln för kundernas
|
||||
# leverantörsuppgifter.
|
||||
|
||||
resource "kubernetes_deployment_v1" "plattform" {
|
||||
metadata {
|
||||
name = "plattform"
|
||||
namespace = kubernetes_namespace_v1.denna.metadata[0].name
|
||||
labels = merge(local.etiketter, { app = "plattform" })
|
||||
}
|
||||
|
||||
spec {
|
||||
replicas = var.repliker.plattform
|
||||
|
||||
selector {
|
||||
match_labels = { app = "plattform" }
|
||||
}
|
||||
|
||||
template {
|
||||
metadata {
|
||||
labels = merge(local.etiketter, { app = "plattform" })
|
||||
}
|
||||
|
||||
spec {
|
||||
automount_service_account_token = false
|
||||
|
||||
security_context {
|
||||
run_as_non_root = true
|
||||
|
||||
seccomp_profile {
|
||||
type = "RuntimeDefault"
|
||||
}
|
||||
}
|
||||
|
||||
container {
|
||||
name = "plattform"
|
||||
image = local.tjanster.plattform.bild
|
||||
|
||||
port {
|
||||
container_port = local.port_container
|
||||
}
|
||||
|
||||
env {
|
||||
name = "JWT_SECRET"
|
||||
|
||||
value_from {
|
||||
secret_key_ref {
|
||||
name = kubernetes_secret_v1.hemligheter.metadata[0].name
|
||||
key = "jwt-secret"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Krypterar kundernas leverantörsuppgifter i vila. Saknas den
|
||||
# sparas ingenting alls — tjänsten failar closed i stället för
|
||||
# att lagra i klartext.
|
||||
env {
|
||||
name = "INTEGRATION_NYCKEL"
|
||||
|
||||
value_from {
|
||||
secret_key_ref {
|
||||
name = kubernetes_secret_v1.hemligheter.metadata[0].name
|
||||
key = "integration-nyckel"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
env {
|
||||
name = "POSTGRES_LOSENORD"
|
||||
|
||||
value_from {
|
||||
secret_key_ref {
|
||||
name = kubernetes_secret_v1.hemligheter.metadata[0].name
|
||||
key = "postgres-losenord"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
env {
|
||||
name = "DATABASE_URL"
|
||||
value = "postgresql://plattform:$(POSTGRES_LOSENORD)@postgres:5432/felsokning"
|
||||
}
|
||||
|
||||
# Klienten serveras från samma domän som API:t, så CORS behöver
|
||||
# inte vara öppet.
|
||||
env {
|
||||
name = "TILLATNA_URSPRUNG"
|
||||
value = "https://${var.doman}"
|
||||
}
|
||||
|
||||
env {
|
||||
name = "REGISTRERING_OPPEN"
|
||||
value = var.registrering_oppen ? "true" : "false"
|
||||
}
|
||||
|
||||
env {
|
||||
name = "TILLAT_INTERNA_UPPSLAG"
|
||||
value = var.tillat_interna_uppslag ? "true" : "false"
|
||||
}
|
||||
|
||||
resources {
|
||||
requests = { cpu = "100m", memory = "128Mi" }
|
||||
limits = { cpu = "1", memory = "512Mi" }
|
||||
}
|
||||
|
||||
readiness_probe {
|
||||
http_get {
|
||||
path = "/halsa"
|
||||
port = local.port_container
|
||||
}
|
||||
|
||||
initial_delay_seconds = 3
|
||||
period_seconds = 10
|
||||
}
|
||||
|
||||
liveness_probe {
|
||||
http_get {
|
||||
path = "/halsa"
|
||||
port = local.port_container
|
||||
}
|
||||
|
||||
period_seconds = 15
|
||||
}
|
||||
|
||||
security_context {
|
||||
allow_privilege_escalation = false
|
||||
read_only_root_filesystem = true
|
||||
|
||||
capabilities {
|
||||
drop = ["ALL"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "kubernetes_service_v1" "plattform" {
|
||||
metadata {
|
||||
name = "plattform"
|
||||
namespace = kubernetes_namespace_v1.denna.metadata[0].name
|
||||
labels = merge(local.etiketter, { app = "plattform" })
|
||||
}
|
||||
|
||||
spec {
|
||||
selector = { app = "plattform" }
|
||||
|
||||
port {
|
||||
port = local.port_tjanst
|
||||
target_port = local.port_container
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "kubernetes_horizontal_pod_autoscaler_v2" "plattform" {
|
||||
metadata {
|
||||
name = "plattform"
|
||||
namespace = kubernetes_namespace_v1.denna.metadata[0].name
|
||||
labels = local.etiketter
|
||||
}
|
||||
|
||||
spec {
|
||||
min_replicas = var.repliker.plattform
|
||||
max_replicas = var.max_repliker.plattform
|
||||
|
||||
scale_target_ref {
|
||||
api_version = "apps/v1"
|
||||
kind = "Deployment"
|
||||
name = kubernetes_deployment_v1.plattform.metadata[0].name
|
||||
}
|
||||
|
||||
metric {
|
||||
type = "Resource"
|
||||
|
||||
resource {
|
||||
name = "cpu"
|
||||
|
||||
target {
|
||||
type = "Utilization"
|
||||
average_utilization = 70
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "kubernetes_pod_disruption_budget_v1" "plattform" {
|
||||
metadata {
|
||||
name = "plattform"
|
||||
namespace = kubernetes_namespace_v1.denna.metadata[0].name
|
||||
labels = local.etiketter
|
||||
}
|
||||
|
||||
spec {
|
||||
min_available = 1
|
||||
|
||||
selector {
|
||||
match_labels = { app = "plattform" }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user