Skip to content

Add pgaccess feature - #526

Open
jhrv wants to merge 6 commits into
mainfrom
pgaccess
Open

jhrv wants to merge 6 commits into
mainfrom
pgaccess

Conversation

@jhrv

@jhrv jhrv commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

This pull request introduces support for personal Postgres access management via the API, including new authorizations, RBAC rules, GraphQL mutations and queries, and comprehensive integration tests. The changes ensure that users can request, retrieve, and audit personal Postgres access securely, with fine-grained authorization checks and proper activity logging.

API and Authorization Enhancements:

  • Added a new postgres:access:read authorization, granting team members and owners permission to read personal Postgres access status and credentials. This is enforced via a new CanReadPostgresAccess function and corresponding database migration. [1] [2]
  • Updated RBAC rules to allow get and create verbs for the postgresaccesses resource in the nais.io API group, enabling the necessary Kubernetes permissions for managing Postgres access. [1] [2]

GraphQL API Additions:

  • Introduced the createPostgresAccess mutation and postgresAccessConnection and postgresAccess queries, along with their input types and resolvers, to support creation and retrieval of personal Postgres access and connection credentials. [1] [2] [3] [4] [5]
  • Extended the activity log GraphQL resolvers to support new activity log entry types for personal Postgres access creation and connection retrieval.

Integration Tests and Fixtures:

  • Added comprehensive integration tests in create_postgres_access.lua to cover authorization, error cases (such as unknown/unavailable instances), successful access creation, credential retrieval, and auditing.
  • Introduced Kubernetes resource fixtures for various Postgres access scenarios (ready, expired, pending, missing secret) and Postgres cluster states (ready, progressing) to support the integration tests. [1] [2] [3] [4] [5] [6] [7]

These changes collectively enable secure and auditable management of personal Postgres access for team members via the API.

References:
[1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11] [12] [13] [14] [15] [16] [17] [18]

@jhrv
jhrv requested a review from a team as a code owner September 17, 2026 12:27
Comment thread internal/database/migrations/0075_add_postgres_access_read_authorization.sql Outdated
Comment thread internal/persistence/postgres/models.go Outdated
Comment thread internal/persistence/postgres/node.go Outdated
Comment thread internal/persistence/postgres/queries.go Outdated
Comment thread internal/persistence/postgres/queries.go
Comment thread internal/persistence/postgres/queries.go Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The status query has unresolved authorization behavior and requires additional integration coverage.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds secure, auditable personal Postgres access management through GraphQL, authorization, RBAC, persistence, and integration tests.

Changes:

  • Adds access creation, status, and credential retrieval.
  • Adds authorization, auditing, migration, and Kubernetes RBAC support.
  • Adds models, resolvers, generated schema code, tests, and fixtures.
File summaries
File Summary
internal/persistence/postgres/queries.go Access creation and retrieval logic
internal/persistence/postgres/queries_test.go Unit tests for access handling
internal/persistence/postgres/node.go Node identification support
internal/persistence/postgres/models.go Access models and validation
internal/persistence/postgres/activitylog.go Access audit transformations
internal/graph/schema/postgres.graphqls GraphQL schema additions
internal/graph/postgres.resolvers.go GraphQL resolvers
internal/graph/gengql/schema.generated.go Generated schema execution code
internal/graph/gengql/root_.generated.go Generated resolver metadata
internal/graph/gengql/activitylog.generated.go Generated activity-log support
internal/database/migrations/0075_add_postgres_access_read_authorization.sql Read authorization migration
internal/auth/authz/queries.go Postgres access authorization checks
integration_tests/k8s_resources/create_postgres_access/dev/someteamname/secret_ready_access_credentials.yaml Ready credential fixture
integration_tests/k8s_resources/create_postgres_access/dev/someteamname/postgres_progressing.yaml Progressing instance fixture
integration_tests/k8s_resources/create_postgres_access/dev/someteamname/postgres_foobar.yaml Available instance fixture
integration_tests/k8s_resources/create_postgres_access/dev/someteamname/postgres_access_ready.yaml Ready access fixture
integration_tests/k8s_resources/create_postgres_access/dev/someteamname/postgres_access_pending-access.yaml Pending access fixture
integration_tests/k8s_resources/create_postgres_access/dev/someteamname/postgres_access_missing-secret-access.yaml Missing-secret fixture
integration_tests/k8s_resources/create_postgres_access/dev/someteamname/postgres_access_expired-access.yaml Expired access fixture
integration_tests/create_postgres_access.lua Integration coverage for access flows
charts/console-backend-rbac/templates/rbac.yaml Kubernetes RBAC permissions
Review details

Files not reviewed (2)

  • internal/graph/gengql/activitylog.generated.go: Generated file
  • internal/graph/gengql/schema.generated.go: Generated file

Suppressed comments (15)

internal/graph/schema/postgres.graphqls:266

  • The newly added activity-log schema types/data type are missing the required public descriptions (and the data fields are undocumented). Add descriptions for the type and each field before exposing this schema.
type PostgresPersonalAccessCreatedActivityLogEntryData {
	username: String!
	expiresAt: Time!
	reason: String!

internal/graph/schema/postgres.graphqls:338

  • The new CreatePostgresAccessPayload type and its fields are missing the required public descriptions. Add developer-facing descriptions before exposing this payload in the schema.
type CreatePostgresAccessPayload {
	"Name of the newly created PostgresAccess resource."
	name: String!
	"Server-controlled expiry for this personal access."
	expiresAt: Time!

internal/graph/schema/postgres.graphqls:345

  • The new input type and its fields are largely undocumented; only reason has a description. Add descriptions for the input and every field, including the access-level and client-key semantics.
input CreatePostgresAccessInput {
	postgresInstance: String!
	teamSlug: Slug!
	environmentName: String!
	accessLevel: PostgresAccessLevel!

internal/graph/schema/postgres.graphqls:354

  • The new PostgresAccessLevel enum and all of its values lack descriptions. Add developer-facing descriptions for the enum and each value.
enum PostgresAccessLevel {
	READ
	READWRITE
	READWRITECREATE

internal/graph/schema/postgres.graphqls:412

  • The new PostgresAccess type and its id/name fields omit the required descriptions. Add a type description and explain these fields in the public schema.
type PostgresAccess implements Node {
	id: ID!
	name: String!

internal/graph/schema/postgres.graphqls:435

  • The new PostgresAccessState enum and all values lack descriptions. Add developer-facing descriptions for the enum and each state.
enum PostgresAccessState {
	PENDING
	READY
	FAILED
	EXPIRED

internal/graph/schema/postgres.graphqls:450

  • The new connection input type and its fields are missing public descriptions. Add descriptions for the type and each input field.
input PostgresAccessConnectionInput {
	name: String!
	teamSlug: Slug!
	environmentName: String!

internal/graph/schema/postgres.graphqls:457

  • The new connection payload type and its fields are missing public descriptions. Add descriptions for the type and each returned field, especially the sensitive credential fields.
type PostgresAccessConnectionPayload {
	password: String!
	caCertificate: String!
	serverName: String!
	tunnel: PostgresAccessConnectionTunnel!

internal/graph/schema/postgres.graphqls:463

  • The new connection tunnel type and both fields are missing public descriptions. Add descriptions explaining the endpoint and gateway key.
type PostgresAccessConnectionTunnel {
	endpoint: String!
	gatewayPublicKey: String!
}

internal/graph/schema/postgres.graphqls:438

  • The new PostgresAccessTunnel type itself lacks a description even though its fields are documented. Add a developer-facing type description.
type PostgresAccessTunnel {

internal/graph/schema/postgres.graphqls:273

  • The new personal-access-connection activity-log type and its fields are all missing public descriptions. Add descriptions for the type and every field.
type PostgresPersonalAccessConnectionActivityLogEntry implements ActivityLogEntry & Node {
	id: ID!
	actor: String!
	createdAt: Time!
	message: String!

internal/graph/schema/postgres.graphqls:451

  • The connection input and the response types below are public schema additions but have no descriptions on their types or fields. Add developer-facing descriptions for the input, payload, tunnel, and every field before publishing them.
input PostgresAccessConnectionInput {
	name: String!
	teamSlug: Slug!
	environmentName: String!
}

internal/graph/schema/postgres.graphqls:457

  • The connection payload and tunnel types expose credentials and connection metadata without GraphQL descriptions. Document the types and each field, particularly password and caCertificate, so consumers understand the sensitivity and purpose of these values.
type PostgresAccessConnectionPayload {
	password: String!
	caCertificate: String!
	serverName: String!
	tunnel: PostgresAccessConnectionTunnel!

internal/persistence/postgres/queries.go:156

  • CanReadPostgresAccess grants this operation to team members, but this additional owner check rejects every non-owner with PostgresAccess not found. That makes the new read authorization ineffective for the status query and conflicts with the migration's Team member role; keep the owner-only check for credentials if intended, but allow authorized team members to read status.
	actor := authz.ActorFromContext(ctx)
	if actor == nil || access.Username != actor.User.Identity() {
		return nil, apierror.Errorf("PostgresAccess %q not found", name)
	}

internal/persistence/postgres/queries.go:128

  • The new public status query/Node lookup is not exercised by the added integration tests; they cover only connection retrieval and helper functions. Add an integration test for postgresAccess (including pending/expired state mapping and a different-owner request) because this path contains the ownership and status exposure checks.
func GetPostgresAccessByIdent(ctx context.Context, id ident.Ident) (*PostgresAccess, error) {
	teamSlug, environmentName, name, err := parseAccessIdent(id)
	if err != nil {
		return nil, err
	}

	return GetPostgresAccess(ctx, name, teamSlug, environmentName)
  • Files reviewed: 25/28 changed files
  • Comments generated: 2
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread internal/graph/postgres.resolvers.go
Comment thread internal/graph/schema/postgres.graphqls
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants