b147694e31
Tio av tolv fynd stängda, två reducerade med skälen utskrivna. Varje
stängning prövas av integrationssviten mot riktig Postgres — 144
kontroller, upp från 100.
T-1 Händelsenyckeln är nu (arende_id, id). Det finns inget delat
namnrum kvar att ockupera, så attacken saknar yta i stället för att
vara mildrad. Inom ett ärende bedöms en kollision på klientens
avtryck — en hash av det kanoniserade innehåll klienten skickade,
taget före serverns egna fält och före krypteringen, eftersom
varken raden eller nyttolasten går att jämföra. Identiskt innehåll
är fortfarande idempotent; samma id med annat innehåll ger 409 och
skriver ingenting alls, inte heller resten av satsen. Samma attack
en nivå upp — ärende-id är lika förutsägbart — stängs separat: ett
id som ägs av en annan organisation ger 409 i stället för att tyst
låta bli, vilket tidigare lämnade offrets ärende oskapat och varje
senare synk svarande 404.
T-2 Mätdonet slås upp i registret. Beteckning och kalibrering HÄRLEDS
därifrån och skriver över det klienten skickade. Okänt mätdon ger
400. Utgånget avvisas inte — mätningen gjordes — men registrets
datum följer med, så graderingen faller på registrets uppgift. Ett
mätvärde utan mätdon får sina påstådda uppgifter borttagna.
T-3 Reducerad, inte stängd. Personnycklarna kuverteras under en
huvudnyckel utanför databasen. En återställd dump ger nycklar som
inte öppnas — verifierat genom att starta om utan huvudnyckeln. Vad
som återstår står utskrivet: en backup tagen FÖRE en radering, plus
huvudnyckeln, återställer fortfarande uppgifterna.
Därtill: gallringen verkställs nu av ett eget jobb och grupperar på det
blindade fordonsindexet så att en delad nyckel inte gallras för tidigt
(T-4) · AI-avlästa mätvärden bär härkomst (T-5) · bcrypt kostnad 12
(T-6) · åtkomstloggen och raderingsregistret är append-only, med en
smalare regel för personnycklarna som måste kunna förstöras (T-7) ·
åtkomstloggen dokumenterad (T-8) · ett externt regelpaket utan signatur
spärrar avslut (T-9) · CORS faller inte längre öppet (T-10) · exp krävs
i token (T-11) · react-router 7 (T-12, med den kvarvarande avvikelsen
motiverad).
Den motspelande hyresgästen som revisionen efterlyste finns nu som
testform och körs i CI.
---- Vad härdningen själv avslöjade -----------------------------------
Två av rättelserna var kortvarigt fel på samma sätt som fynden, och
bägge fångades bara av att jag försökte bevisa dem:
T-7-testet var grönt mot en TOM tabell. En radnivåtrigger har inga
rader att fyra på, så delete lyckades och kontrollen mätte ingenting.
T-6-testet påstod anropet, inte kostnaden. toContain("gen_salt('bf')")
hade accepterat kostnad 6 för evigt — och gjorde det, så länge det
fanns.
Bägge är mönstret revisionen namngav: en kontroll som är riktig i sina
egna termer och oprövad vid sin gräns. Det gäller tester lika mycket som
kod.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012EQg3rJsrQ1ZNTvkzmQAtt
122 lines
3.5 KiB
Terraform
122 lines
3.5 KiB
Terraform
# Namnrymden, tjänstekontot och hemligheterna.
|
|
#
|
|
# Hemligheterna bor i AWS Secrets Manager, inte här. External Secrets
|
|
# speglar dem in i klustret så att podarna kan läsa dem som vanliga
|
|
# miljövariabler — men sanningskällan är Secrets Manager, med rotation,
|
|
# revisionsspår per läsning och kryptering med egen KMS-nyckel.
|
|
#
|
|
# Terraform ser aldrig värdena, och det är själva poängen: en hemlighet
|
|
# som passerar Terraform hamnar i tillståndsfilen.
|
|
|
|
resource "kubernetes_namespace_v1" "denna" {
|
|
metadata {
|
|
name = var.namnrymd
|
|
labels = local.etiketter
|
|
}
|
|
}
|
|
|
|
# Tjänstekontot plattformstjänsten kör som. IRSA-annoteringen binder det
|
|
# till rollen basen skapade — och rollen är i sin tur bunden till exakt
|
|
# det här kontot i den här namnrymden. Ingen annan pod kan anta den.
|
|
resource "kubernetes_service_account_v1" "plattform" {
|
|
metadata {
|
|
name = "plattform"
|
|
namespace = kubernetes_namespace_v1.denna.metadata[0].name
|
|
labels = local.etiketter
|
|
|
|
annotations = {
|
|
"eks.amazonaws.com/role-arn" = local.aws.plattform_roll
|
|
}
|
|
}
|
|
}
|
|
|
|
# Var hemligheterna hämtas ifrån. Autentiseringen sker med
|
|
# tjänstekontots roll — inga nycklar att distribuera.
|
|
resource "kubernetes_manifest" "hemlighetskalla" {
|
|
manifest = {
|
|
apiVersion = "external-secrets.io/v1beta1"
|
|
kind = "SecretStore"
|
|
|
|
metadata = {
|
|
name = "aws"
|
|
namespace = kubernetes_namespace_v1.denna.metadata[0].name
|
|
}
|
|
|
|
spec = {
|
|
provider = {
|
|
aws = {
|
|
service = "SecretsManager"
|
|
region = local.aws.region
|
|
|
|
auth = {
|
|
jwt = {
|
|
serviceAccountRef = {
|
|
name = kubernetes_service_account_v1.plattform.metadata[0].name
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
depends_on = [helm_release.external_secrets]
|
|
}
|
|
|
|
# Speglingen. Namnen till vänster är miljövariablerna tjänsterna läser;
|
|
# till höger nycklarna i Secrets Manager.
|
|
resource "kubernetes_manifest" "hemligheter" {
|
|
manifest = {
|
|
apiVersion = "external-secrets.io/v1beta1"
|
|
kind = "ExternalSecret"
|
|
|
|
metadata = {
|
|
name = "felsokning-hemligheter"
|
|
namespace = kubernetes_namespace_v1.denna.metadata[0].name
|
|
}
|
|
|
|
spec = {
|
|
# Roteras en hemlighet i Secrets Manager följer klustret efter inom
|
|
# en timme, utan att någon behöver göra något.
|
|
refreshInterval = "1h"
|
|
|
|
secretStoreRef = {
|
|
name = "aws"
|
|
kind = "SecretStore"
|
|
}
|
|
|
|
target = {
|
|
name = "felsokning-hemligheter"
|
|
creationPolicy = "Owner"
|
|
}
|
|
|
|
data = [
|
|
{
|
|
secretKey = "DATABASE_URL"
|
|
remoteRef = { key = local.aws.hemlighet_databas, property = "url" }
|
|
},
|
|
{
|
|
secretKey = "ANTHROPIC_API_KEY"
|
|
remoteRef = { key = local.aws.hemlighet_app, property = "anthropic_api_key" }
|
|
},
|
|
{
|
|
secretKey = "JWT_SECRET"
|
|
remoteRef = { key = local.aws.hemlighet_app, property = "jwt_secret" }
|
|
},
|
|
{
|
|
secretKey = "INTEGRATION_NYCKEL"
|
|
remoteRef = { key = local.aws.hemlighet_app, property = "integration_nyckel" }
|
|
},
|
|
# Kuverterar personnycklarna. Utan den går en återställd databas
|
|
# inte att läsa — vilket är avsikten (TÜV T-3).
|
|
{
|
|
secretKey = "PERSONNYCKEL_HUVUD"
|
|
remoteRef = { key = local.aws.hemlighet_app, property = "personnyckel_huvud" }
|
|
},
|
|
]
|
|
}
|
|
}
|
|
|
|
depends_on = [kubernetes_manifest.hemlighetskalla]
|
|
}
|