From bdf4dd26af412fad32042c6515145c28426d1edf Mon Sep 17 00:00:00 2001 From: Johnny Fredheim Horvi Date: Fri, 2 Oct 2026 15:24:26 +0200 Subject: [PATCH] fix: use published relay endpoint for Postgres connections --- integration_tests/create_postgres_access.lua | 8 ++++++- .../dev/someteamname/accesses.yaml | 23 +++++++++++++++++++ internal/persistence/postgres/connection.go | 1 - internal/persistence/postgres/queries.go | 13 ++++++++++- internal/persistence/postgres/queries_test.go | 8 +++++-- 5 files changed, 48 insertions(+), 5 deletions(-) diff --git a/integration_tests/create_postgres_access.lua b/integration_tests/create_postgres_access.lua index c8952a564..618043233 100644 --- a/integration_tests/create_postgres_access.lua +++ b/integration_tests/create_postgres_access.lua @@ -341,7 +341,7 @@ Test.gql("PostgresAccess connection returns credentials only to its owner", func caCertificate = "test-ca-certificate", serverName = "pg-foobar-main-a4f04c0c-rw.someteamname.svc.cluster.local", username = "user-foobar-role", - relayEndpoint = Contains("https://relay.external.dev."), + relayEndpoint = "https://relay.external.dev.nav.cloud.nais.io:8443", relayAccess = "someteamname/mapped-access", relayToken = "AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8", }, @@ -392,6 +392,12 @@ Test.gql("PostgresAccess connection is null without error until the access is re end end) +Test.gql("PostgresAccess connection is null before relay endpoint is published", function(t) + t.addHeader("x-user-email", user:email()) + t.query [[query { team(slug: "someteamname") { environment(name: "dev") { postgresAccess(name: "ready-no-endpoint-access") { connection { relayEndpoint } } } } }]] + t.check { data = { team = { environment = { postgresAccess = { connection = Null } } } } } +end) + Test.gql("PostgresAccess connection fails when a ready access has no secrets", function(t) t.addHeader("x-user-email", user:email()) t.query [[query { team(slug: "someteamname") { environment(name: "dev") { postgresAccess(name: "missing-secret-access") { state connection { password } } } } }]] diff --git a/integration_tests/k8s_resources/create_postgres_access/dev/someteamname/accesses.yaml b/integration_tests/k8s_resources/create_postgres_access/dev/someteamname/accesses.yaml index 54d692020..2b256863f 100644 --- a/integration_tests/k8s_resources/create_postgres_access/dev/someteamname/accesses.yaml +++ b/integration_tests/k8s_resources/create_postgres_access/dev/someteamname/accesses.yaml @@ -13,6 +13,7 @@ spec: status: databaseRole: user-foobar-role relayAccess: mapped-access + relayEndpoint: https://relay.external.dev.nav.cloud.nais.io:8443 tokenSecret: ready-access-relay-token serverName: pg-foobar-main-a4f04c0c-rw.someteamname.svc.cluster.local serverCASecret: pg-foobar-ca @@ -24,6 +25,27 @@ status: --- apiVersion: nais.io/v1 kind: PostgresAccess +metadata: + name: ready-no-endpoint-access + namespace: someteamname + uid: 44444444-4444-4444-8444-444444444444 +spec: + postgresBranch: foobar-main-a4f04c0c + username: user@usersen.com + accessLevel: read + expiresAt: "2099-09-17T12:00:00Z" +status: + databaseRole: user-foobar-role + relayAccess: ready-no-endpoint-access + tokenSecret: ready-no-endpoint-access-relay-token + serverName: pg-foobar-main-a4f04c0c-rw.someteamname.svc.cluster.local + serverCASecret: pg-foobar-ca + conditions: + - type: Ready + status: "True" +--- +apiVersion: nais.io/v1 +kind: PostgresAccess metadata: name: recovered-access namespace: someteamname @@ -98,6 +120,7 @@ spec: status: databaseRole: user-foobar-role relayAccess: missing-secret-access + relayEndpoint: https://relay.external.dev.nav.cloud.nais.io:8443 tokenSecret: missing-secret-access-relay-token serverName: pg-foobar-main-a4f04c0c-rw.someteamname.svc.cluster.local serverCASecret: pg-foobar-ca diff --git a/internal/persistence/postgres/connection.go b/internal/persistence/postgres/connection.go index d23f585c5..13bd6061f 100644 --- a/internal/persistence/postgres/connection.go +++ b/internal/persistence/postgres/connection.go @@ -73,7 +73,6 @@ func loadPostgresAccessConnection(ctx context.Context, access *unstructured.Unst return apierror.Errorf("PostgresAccess %q is not ready", access.GetName()) } connection.ServerName = serverName - connection.RelayEndpoint = fmt.Sprintf("https://relay.external.%s.%s.cloud.nais.io:8443", input.EnvironmentName, fromContext(ctx).tenantName) relayName, _, err := unstructured.NestedString(access.Object, "status", "relayAccess") if err != nil || relayName == "" { return apierror.Errorf("PostgresAccess %q is not ready", access.GetName()) diff --git a/internal/persistence/postgres/queries.go b/internal/persistence/postgres/queries.go index 439f206de..b42006b80 100644 --- a/internal/persistence/postgres/queries.go +++ b/internal/persistence/postgres/queries.go @@ -244,6 +244,13 @@ func GetPostgresAccessConnection(ctx context.Context, input PostgresAccessConnec if state, _ := postgresAccessState(resource, resource.Spec.ExpiresAt.Time); state != PostgresAccessStateReady || access.GetDeletionTimestamp() != nil { return nil, nil } + endpoint, _, err := unstructured.NestedString(access.Object, "status", "relayEndpoint") + if err != nil { + return nil, fmt.Errorf("reading relay endpoint for PostgresAccess %q: %w", input.Name, err) + } + if endpoint == "" { + return nil, nil + } connection, credentialSecretName, err := postgresAccessConnectionDetails(access, time.Now()) if err != nil { @@ -307,7 +314,11 @@ func postgresAccessConnectionDetails(access *unstructured.Unstructured, now time if obj.Status.RelayAccess == "" || obj.Status.TokenSecret == "" || obj.Status.DatabaseRole == "" || obj.Status.ServerName == "" || obj.Status.ServerCASecret == "" { return nil, "", apierror.Errorf("PostgresAccess %q is not ready", access.GetName()) } - return &PostgresAccessConnectionDetails{}, obj.Status.TokenSecret, nil + endpoint, _, err := unstructured.NestedString(access.Object, "status", "relayEndpoint") + if err != nil || endpoint == "" { + return nil, "", apierror.Errorf("PostgresAccess %q is not ready", access.GetName()) + } + return &PostgresAccessConnectionDetails{RelayEndpoint: endpoint}, obj.Status.TokenSecret, nil } func toPostgresAccess(u *unstructured.Unstructured, teamSlug slug.Slug, environmentName string) (*PostgresAccess, error) { diff --git a/internal/persistence/postgres/queries_test.go b/internal/persistence/postgres/queries_test.go index 5df4e4751..caa2f3d69 100644 --- a/internal/persistence/postgres/queries_test.go +++ b/internal/persistence/postgres/queries_test.go @@ -153,6 +153,7 @@ func TestPostgresAccessConnectionDetails(t *testing.T) { "status": map[string]any{ "databaseRole": "personal-role", "relayAccess": "access", + "relayEndpoint": "https://relay.external.dev.nav.cloud.nais.io:8443", "tokenSecret": "access-relay-token", "serverName": "pg-orders-rw.team.svc.cluster.local", "serverCASecret": "pg-orders-ca", @@ -179,6 +180,9 @@ func TestPostgresAccessConnectionDetails(t *testing.T) { {name: "missing relay mapping", edit: func(u *unstructured.Unstructured) { unstructured.RemoveNestedField(u.Object, "status", "relayAccess") }, want: "not ready"}, + {name: "missing relay endpoint", edit: func(u *unstructured.Unstructured) { + unstructured.RemoveNestedField(u.Object, "status", "relayEndpoint") + }, want: "not ready"}, {name: "missing server name", edit: func(u *unstructured.Unstructured) { unstructured.RemoveNestedField(u.Object, "status", "serverName") }, want: "not ready"}, @@ -205,8 +209,8 @@ func TestPostgresAccessConnectionDetails(t *testing.T) { if secretName != "access-relay-token" { t.Errorf("secret name = %q", secretName) } - if got == nil { - t.Fatal("connection is nil") + if got == nil || got.RelayEndpoint != "https://relay.external.dev.nav.cloud.nais.io:8443" { + t.Fatalf("connection endpoint = %v, want status endpoint", got) } }) }