Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions apis/cluster/postgresql/v1alpha1/grant_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ type Routine struct {
}

// GrantParameters define the desired state of a PostgreSQL grant instance.
// +kubebuilder:validation:XValidation:rule="!has(self.withInherit) || has(self.memberOf) || has(self.memberOfRef) || has(self.memberOfSelector)",message="withInherit may only be set on memberOf grants"
type GrantParameters struct {
// Privileges to be granted.
// See https://www.postgresql.org/docs/current/sql-grant.html for available privileges.
Expand Down Expand Up @@ -406,6 +407,15 @@ type GrantParameters struct {
// +optional
// +kubebuilder:validation:items:Pattern:=^[a-zA-Z_][a-zA-Z0-9_$]*$
ForeignServers []string `json:"foreignServers,omitempty"`

// WithInherit controls whether the grantee automatically inherits the privileges
// of the granted role. When set to false, emits WITH INHERIT FALSE (PostgreSQL 16+),
// granting membership without automatic privilege inheritance. Only valid when
// memberOf is set. When omitted, PostgreSQL's default behavior (inherit true) applies.
// Note: this field is only evaluated when non-nil. Removing it from the manifest
// does NOT revert the database-side setting; set withInherit: true explicitly to revert.
// +optional
WithInherit *bool `json:"withInherit,omitempty"`
}

// A GrantStatus represents the observed state of a Grant.
Expand Down
5 changes: 5 additions & 0 deletions apis/cluster/postgresql/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions apis/namespaced/postgresql/v1alpha1/grant_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ type Routine struct {
}

// GrantParameters define the desired state of a PostgreSQL grant instance.
// +kubebuilder:validation:XValidation:rule="!has(self.withInherit) || has(self.memberOf) || has(self.memberOfRef) || has(self.memberOfSelector)",message="withInherit may only be set on memberOf grants"
type GrantParameters struct {
// Privileges to be granted.
// See https://www.postgresql.org/docs/current/sql-grant.html for available privileges.
Expand Down Expand Up @@ -408,6 +409,15 @@ type GrantParameters struct {
// +optional
// +kubebuilder:validation:items:Pattern:=^[a-zA-Z_][a-zA-Z0-9_$]*$
ForeignServers []string `json:"foreignServers,omitempty"`

// WithInherit controls whether the grantee automatically inherits the privileges
// of the granted role. When set to false, emits WITH INHERIT FALSE (PostgreSQL 16+),
// granting membership without automatic privilege inheritance. Only valid when
// memberOf is set. When omitted, PostgreSQL's default behavior (inherit true) applies.
// Note: this field is only evaluated when non-nil. Removing it from the manifest
// does NOT revert the database-side setting; set withInherit: true explicitly to revert.
// +optional
WithInherit *bool `json:"withInherit,omitempty"`
}

// A GrantStatus represents the observed state of a Grant.
Expand Down
5 changes: 5 additions & 0 deletions apis/namespaced/postgresql/v1alpha1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion build
Submodule build updated 1 files
+1 −1 makelib/xpkg.mk
25 changes: 25 additions & 0 deletions examples/cluster/postgresql/grant-with-inherit-false.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Demonstrates WITH INHERIT FALSE on a role membership grant (PostgreSQL 16+).
#
# Use case: RDS IAM database authentication with a shared admin role.
#
# 1. An "admin" role holds rds_iam membership so that migration tools
# (Flyway, Liquibase, Atlas) can authenticate via IAM tokens.
# 2. The RDS master user needs membership in "admin" in order to run
# ALTER DEFAULT PRIVILEGES FOR ROLE admin — but must NOT inherit
# the rds_iam privilege, which would break its password authentication.
#
# GRANT admin TO master_user WITH INHERIT FALSE satisfies both requirements:
# the master user can SET ROLE admin (for ALTER DEFAULT PRIVILEGES) while
# retaining its own password-based login.
---
apiVersion: postgresql.sql.crossplane.io/v1alpha1
kind: Grant
metadata:
name: grant-master-user-membership-no-inherit
spec:
forProvider:
withInherit: false
roleRef:
name: master-user
memberOfRef:
name: admin-role
13 changes: 13 additions & 0 deletions package/crds/postgresql.sql.crossplane.io_grants.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,15 @@ spec:
pattern: ^[a-zA-Z_][a-zA-Z0-9_$]*$
type: string
type: array
withInherit:
description: |-
WithInherit controls whether the grantee automatically inherits the privileges
of the granted role. When set to false, emits WITH INHERIT FALSE (PostgreSQL 16+),
granting membership without automatic privilege inheritance. Only valid when
memberOf is set. When omitted, PostgreSQL's default behavior (inherit true) applies.
Note: this field is only evaluated when non-nil. Removing it from the manifest
does NOT revert the database-side setting; set withInherit: true explicitly to revert.
type: boolean
withOption:
description: |-
WithOption allows an option to be set on the grant.
Expand All @@ -472,6 +481,10 @@ spec:
- GRANT
type: string
type: object
x-kubernetes-validations:
- message: withInherit may only be set on memberOf grants
rule: '!has(self.withInherit) || has(self.memberOf) || has(self.memberOfRef)
|| has(self.memberOfSelector)'
managementPolicies:
default:
- '*'
Expand Down
13 changes: 13 additions & 0 deletions package/crds/postgresql.sql.m.crossplane.io_grants.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,15 @@ spec:
pattern: ^[a-zA-Z_][a-zA-Z0-9_$]*$
type: string
type: array
withInherit:
description: |-
WithInherit controls whether the grantee automatically inherits the privileges
of the granted role. When set to false, emits WITH INHERIT FALSE (PostgreSQL 16+),
granting membership without automatic privilege inheritance. Only valid when
memberOf is set. When omitted, PostgreSQL's default behavior (inherit true) applies.
Note: this field is only evaluated when non-nil. Removing it from the manifest
does NOT revert the database-side setting; set withInherit: true explicitly to revert.
type: boolean
withOption:
description: |-
WithOption allows an option to be set on the grant.
Expand All @@ -482,6 +491,10 @@ spec:
- GRANT
type: string
type: object
x-kubernetes-validations:
- message: withInherit may only be set on memberOf grants
rule: '!has(self.withInherit) || has(self.memberOf) || has(self.memberOfRef)
|| has(self.memberOfSelector)'
managementPolicies:
default:
- '*'
Expand Down
57 changes: 50 additions & 7 deletions pkg/controller/cluster/postgresql/grant/reconciler.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ const (
errInvalidParams = "invalid parameters for grant type %s"
errGetServerVersion = "cannot get server version"
errMemberOfWithDatabaseOrPrivileges = "cannot set privileges or database in the same grant as memberOf"
errWithInheritOnlyForMemberOf = "withInherit is only valid for memberOf grants"
errInheritRequiresPG16 = "withInherit requires PostgreSQL 16 or later (server version %d)"

maxConcurrency = 5
)
Expand Down Expand Up @@ -280,6 +282,9 @@ func createGrantQueriesWithVersion(gp v1alpha1.GrantParameters, ql *[]xsql.Query
case v1alpha1.RoleForeignServer:
return createForeignServerGrantQueries(gp, ql, ro)
case v1alpha1.RoleMember:
if gp.WithInherit != nil && serverVersion < 160000 {
return errors.Errorf(errInheritRequiresPG16, serverVersion)
}
return createMemberGrantQueries(gp, ql, ro)
case v1alpha1.RoleRoutine:
return createRoutineGrantQueries(gp, ql, ro)
Expand All @@ -303,12 +308,33 @@ func createMemberGrantQueries(gp v1alpha1.GrantParameters, ql *[]xsql.Query, ro
*ql = append(*ql,
xsql.Query{String: fmt.Sprintf("REVOKE %s FROM %s", mo, ro)},
xsql.Query{String: fmt.Sprintf("GRANT %s TO %s %s", mo, ro,
withOption(gp.WithOption),
membershipWithClauses(gp.WithOption, gp.WithInherit),
)},
)
return nil
}

// membershipWithClauses builds the WITH clause for role membership GRANTs,
// combining the optional ADMIN/SET option and the optional INHERIT flag.
// On PostgreSQL 16+, multiple options are comma-separated: WITH ADMIN OPTION, INHERIT FALSE.
func membershipWithClauses(option *v1alpha1.GrantOption, inherit *bool) string {
var parts []string
if option != nil {
parts = append(parts, fmt.Sprintf("%s OPTION", string(*option)))
}
if inherit != nil {
if *inherit {
parts = append(parts, "INHERIT TRUE")
} else {
parts = append(parts, "INHERIT FALSE")
}
}
if len(parts) == 0 {
return ""
}
return "WITH " + strings.Join(parts, ", ")
}

func createRoutineGrantQueries(gp v1alpha1.GrantParameters, ql *[]xsql.Query, ro string) error {
if gp.Database == nil || gp.Schema == nil || len(gp.Routines) < 1 || gp.Role == nil || len(gp.Privileges) < 1 {
return errors.Errorf(errInvalidParams, v1alpha1.RoleRoutine)
Expand Down Expand Up @@ -603,6 +629,10 @@ func resolveGrantType(gp v1alpha1.GrantParameters) (v1alpha1.GrantType, error) {
return v1alpha1.RoleMember, nil
}

if gp.WithInherit != nil {
return "", errors.New(errWithInheritOnlyForMemberOf)
}

return gp.IdentifyGrantType()
}

Expand Down Expand Up @@ -771,6 +801,9 @@ func selectGrantQueryWithVersion(gp v1alpha1.GrantParameters, q *xsql.Query, ser
case v1alpha1.RoleForeignServer:
return selectForeignServerGrantQuery(gp, q)
case v1alpha1.RoleMember:
if gp.WithInherit != nil && serverVersion < 160000 {
return errors.Errorf(errInheritRequiresPG16, serverVersion)
}
return selectMemberGrantQuery(gp, q)
case v1alpha1.RoleRoutine:
return selectRoutineGrantQuery(gp, q)
Expand All @@ -791,17 +824,27 @@ func selectMemberGrantQuery(gp v1alpha1.GrantParameters, q *xsql.Query) error {
// A simpler query would use ::regrole to cast the roleid and member oids
// to their role names, but that throws an error for nonexistent roles
// rather than returning false.
q.String = "SELECT EXISTS(SELECT 1 FROM pg_auth_members m " +
"INNER JOIN pg_roles mo ON m.roleid = mo.oid " +
"INNER JOIN pg_roles r ON m.member = r.oid " +
"WHERE r.rolname=$1 AND mo.rolname=$2 AND " +
"m.admin_option = $3)"

q.Parameters = []interface{}{
gp.Role,
gp.MemberOf,
ao,
}

if gp.WithInherit != nil {
// inherit_option is a pg_auth_members column added in PostgreSQL 16.
q.String = "SELECT EXISTS(SELECT 1 FROM pg_auth_members m " +
"INNER JOIN pg_roles mo ON m.roleid = mo.oid " +
"INNER JOIN pg_roles r ON m.member = r.oid " +
"WHERE r.rolname=$1 AND mo.rolname=$2 AND " +
"m.admin_option = $3 AND m.inherit_option = $4)"
q.Parameters = append(q.Parameters, *gp.WithInherit)
} else {
q.String = "SELECT EXISTS(SELECT 1 FROM pg_auth_members m " +
"INNER JOIN pg_roles mo ON m.roleid = mo.oid " +
"INNER JOIN pg_roles r ON m.member = r.oid " +
"WHERE r.rolname=$1 AND mo.rolname=$2 AND " +
"m.admin_option = $3)"
}
return nil
}

Expand Down
Loading