Skip to content

Commit fa2eb0e

Browse files
postgresql_grant: add foreign data wrapper and server support (#109)
* postgresql_grant: add foreign data wrapper support * postgresql_grant: add foreign server support * Complete ACC tests * Apply suggestion for testCheckForeignDataWrapperPrivileges Co-authored-by: Cyril Gaudin <[email protected]> * Address review comments * Update docs * Fix assert condition * Skip tests if not superuser * Add more tests for fdw objects, use skipIfNotSuperuser helper Co-authored-by: Cyril Gaudin <[email protected]>
1 parent 1df64cf commit fa2eb0e

File tree

5 files changed

+348
-15
lines changed

5 files changed

+348
-15
lines changed

postgresql/helpers.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -235,12 +235,14 @@ func sliceContainsStr(haystack []string, needle string) bool {
235235
// allowedPrivileges is the list of privileges allowed per object types in Postgres.
236236
// see: https://www.postgresql.org/docs/current/sql-grant.html
237237
var allowedPrivileges = map[string][]string{
238-
"database": []string{"ALL", "CREATE", "CONNECT", "TEMPORARY", "TEMP"},
239-
"table": []string{"ALL", "SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE", "REFERENCES", "TRIGGER"},
240-
"sequence": []string{"ALL", "USAGE", "SELECT", "UPDATE"},
241-
"schema": []string{"ALL", "CREATE", "USAGE"},
242-
"function": []string{"ALL", "EXECUTE"},
243-
"type": []string{"ALL", "USAGE"},
238+
"database": []string{"ALL", "CREATE", "CONNECT", "TEMPORARY", "TEMP"},
239+
"table": []string{"ALL", "SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE", "REFERENCES", "TRIGGER"},
240+
"sequence": []string{"ALL", "USAGE", "SELECT", "UPDATE"},
241+
"schema": []string{"ALL", "CREATE", "USAGE"},
242+
"function": []string{"ALL", "EXECUTE"},
243+
"type": []string{"ALL", "USAGE"},
244+
"foreign_data_wrapper": []string{"ALL", "USAGE"},
245+
"foreign_server": []string{"ALL", "USAGE"},
244246
}
245247

246248
// validatePrivileges checks that privileges to apply are allowed for this object type.

postgresql/resource_postgresql_grant.go

Lines changed: 85 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ var allowedObjectTypes = []string{
1919
"schema",
2020
"sequence",
2121
"table",
22+
"foreign_data_wrapper",
23+
"foreign_server",
2224
}
2325

2426
var objectTypes = map[string]string{
@@ -125,12 +127,15 @@ func resourcePostgreSQLGrantCreate(db *DBConnection, d *schema.ResourceData) err
125127

126128
// Validate parameters.
127129
objectType := d.Get("object_type").(string)
128-
if d.Get("schema").(string) == "" && objectType != "database" {
130+
if d.Get("schema").(string) == "" && !sliceContainsStr([]string{"database", "foreign_data_wrapper", "foreign_server"}, objectType) {
129131
return fmt.Errorf("parameter 'schema' is mandatory for postgresql_grant resource")
130132
}
131133
if d.Get("objects").(*schema.Set).Len() > 0 && (objectType == "database" || objectType == "schema") {
132134
return fmt.Errorf("cannot specify `objects` when `object_type` is `database` or `schema`")
133135
}
136+
if d.Get("objects").(*schema.Set).Len() != 1 && (objectType == "foreign_data_wrapper" || objectType == "foreign_server") {
137+
return fmt.Errorf("one element must be specified in `objects` when `object_type` is `foreign_data_wrapper` or `foreign_server`")
138+
}
134139
if err := validatePrivileges(d); err != nil {
135140
return err
136141
}
@@ -247,6 +252,46 @@ WHERE grantee = $2
247252
return nil
248253
}
249254

255+
func readForeignDataWrapperRolePrivileges(txn *sql.Tx, d *schema.ResourceData, roleOID int) error {
256+
objects := d.Get("objects").(*schema.Set).List()
257+
fdwName := objects[0].(string)
258+
query := `
259+
SELECT pg_catalog.array_agg(privilege_type)
260+
FROM (
261+
SELECT (pg_catalog.aclexplode(fdwacl)).* FROM pg_catalog.pg_foreign_data_wrapper WHERE fdwname=$1
262+
) as privileges
263+
WHERE grantee = $2
264+
`
265+
266+
var privileges pq.ByteaArray
267+
if err := txn.QueryRow(query, fdwName, roleOID).Scan(&privileges); err != nil {
268+
return fmt.Errorf("could not read privileges for foreign data wrapper %s: %w", fdwName, err)
269+
}
270+
271+
d.Set("privileges", pgArrayToSet(privileges))
272+
return nil
273+
}
274+
275+
func readForeignServerRolePrivileges(txn *sql.Tx, d *schema.ResourceData, roleOID int) error {
276+
objects := d.Get("objects").(*schema.Set).List()
277+
srvName := objects[0].(string)
278+
query := `
279+
SELECT pg_catalog.array_agg(privilege_type)
280+
FROM (
281+
SELECT (pg_catalog.aclexplode(srvacl)).* FROM pg_catalog.pg_foreign_server WHERE srvname=$1
282+
) as privileges
283+
WHERE grantee = $2
284+
`
285+
286+
var privileges pq.ByteaArray
287+
if err := txn.QueryRow(query, srvName, roleOID).Scan(&privileges); err != nil {
288+
return fmt.Errorf("could not read privileges for foreign server %s: %w", srvName, err)
289+
}
290+
291+
d.Set("privileges", pgArrayToSet(privileges))
292+
return nil
293+
}
294+
250295
func readRolePrivileges(txn *sql.Tx, d *schema.ResourceData) error {
251296
role := d.Get("role").(string)
252297
objectType := d.Get("object_type").(string)
@@ -267,6 +312,12 @@ func readRolePrivileges(txn *sql.Tx, d *schema.ResourceData) error {
267312
case "schema":
268313
return readSchemaRolePriviges(txn, d, roleOID)
269314

315+
case "foreign_data_wrapper":
316+
return readForeignDataWrapperRolePrivileges(txn, d, roleOID)
317+
318+
case "foreign_server":
319+
return readForeignServerRolePrivileges(txn, d, roleOID)
320+
270321
case "function":
271322
query = `
272323
SELECT pg_proc.proname, array_remove(array_agg(privilege_type), NULL)
@@ -363,6 +414,22 @@ func createGrantQuery(d *schema.ResourceData, privileges []string) string {
363414
pq.QuoteIdentifier(d.Get("schema").(string)),
364415
pq.QuoteIdentifier(d.Get("role").(string)),
365416
)
417+
case "FOREIGN_DATA_WRAPPER":
418+
fdwName := d.Get("objects").(*schema.Set).List()[0]
419+
query = fmt.Sprintf(
420+
"GRANT %s ON FOREIGN DATA WRAPPER %s TO %s",
421+
strings.Join(privileges, ","),
422+
pq.QuoteIdentifier(fdwName.(string)),
423+
pq.QuoteIdentifier(d.Get("role").(string)),
424+
)
425+
case "FOREIGN_SERVER":
426+
srvName := d.Get("objects").(*schema.Set).List()[0]
427+
query = fmt.Sprintf(
428+
"GRANT %s ON FOREIGN SERVER %s TO %s",
429+
strings.Join(privileges, ","),
430+
pq.QuoteIdentifier(srvName.(string)),
431+
pq.QuoteIdentifier(d.Get("role").(string)),
432+
)
366433
case "TABLE", "SEQUENCE", "FUNCTION":
367434
objects := d.Get("objects").(*schema.Set)
368435
if objects.Len() > 0 {
@@ -407,6 +474,20 @@ func createRevokeQuery(d *schema.ResourceData) string {
407474
pq.QuoteIdentifier(d.Get("schema").(string)),
408475
pq.QuoteIdentifier(d.Get("role").(string)),
409476
)
477+
case "FOREIGN_DATA_WRAPPER":
478+
fdwName := d.Get("objects").(*schema.Set).List()[0]
479+
query = fmt.Sprintf(
480+
"REVOKE ALL PRIVILEGES ON FOREIGN DATA WRAPPER %s FROM %s",
481+
pq.QuoteIdentifier(fdwName.(string)),
482+
pq.QuoteIdentifier(d.Get("role").(string)),
483+
)
484+
case "FOREIGN_SERVER":
485+
srvName := d.Get("objects").(*schema.Set).List()[0]
486+
query = fmt.Sprintf(
487+
"REVOKE ALL PRIVILEGES ON FOREIGN SERVER %s FROM %s",
488+
pq.QuoteIdentifier(srvName.(string)),
489+
pq.QuoteIdentifier(d.Get("role").(string)),
490+
)
410491
case "TABLE", "SEQUENCE", "FUNCTION":
411492
objects := d.Get("objects").(*schema.Set)
412493
if objects.Len() > 0 {
@@ -487,7 +568,7 @@ func checkRoleDBSchemaExists(client *Client, d *schema.ResourceData) (bool, erro
487568

488569
pgSchema := d.Get("schema").(string)
489570

490-
if d.Get("object_type").(string) != "database" && pgSchema != "" {
571+
if !sliceContainsStr([]string{"database", "foreign_data_wrapper", "foreign_server"}, d.Get("object_type").(string)) && pgSchema != "" {
491572
// Connect on this database to check if schema exists
492573
dbTxn, err := startTransaction(client, database)
493574
if err != nil {
@@ -513,7 +594,7 @@ func generateGrantID(d *schema.ResourceData) string {
513594
parts := []string{d.Get("role").(string), d.Get("database").(string)}
514595

515596
objectType := d.Get("object_type").(string)
516-
if objectType != "database" {
597+
if objectType != "database" && objectType != "foreign_data_wrapper" && objectType != "foreign_server" {
517598
parts = append(parts, d.Get("schema").(string))
518599
}
519600
parts = append(parts, objectType)
@@ -532,7 +613,7 @@ func getRolesToGrant(txn *sql.Tx, d *schema.ResourceData) ([]string, error) {
532613
owners := []string{}
533614
objectType := d.Get("object_type")
534615

535-
if objectType == "database" {
616+
if objectType == "database" || objectType == "foreign_data_wrapper" || objectType == "foreign_server" {
536617
return owners, nil
537618
}
538619

0 commit comments

Comments
 (0)