Skip to content
This repository was archived by the owner on Nov 14, 2020. It is now read-only.

Commit 3e2ad5f

Browse files
authored
Merge pull request #123 from jeromepin/implement_grants_for_database
Implement grants on database
2 parents 68cb9d2 + 450634a commit 3e2ad5f

5 files changed

+371
-66
lines changed

postgresql/helpers.go

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
package postgresql
22

33
import (
4+
"database/sql"
45
"fmt"
56
"log"
67
"strings"
78

8-
"database/sql"
9-
109
"github.com/hashicorp/errwrap"
1110
"github.com/hashicorp/terraform-plugin-sdk/helper/schema"
1211
"github.com/lib/pq"
@@ -105,12 +104,21 @@ func sliceContainsStr(haystack []string, needle string) bool {
105104
// allowedPrivileges is the list of privileges allowed per object types in Postgres.
106105
// see: https://www.postgresql.org/docs/current/sql-grant.html
107106
var allowedPrivileges = map[string][]string{
107+
"database": []string{"ALL", "CREATE", "CONNECT", "TEMPORARY", "TEMP"},
108108
"table": []string{"ALL", "SELECT", "INSERT", "UPDATE", "DELETE", "TRUNCATE", "REFERENCES", "TRIGGER"},
109109
"sequence": []string{"ALL", "USAGE", "SELECT", "UPDATE"},
110110
}
111111

112112
// validatePrivileges checks that privileges to apply are allowed for this object type.
113-
func validatePrivileges(objectType string, privileges []interface{}) error {
113+
func validatePrivileges(d *schema.ResourceData) error {
114+
objectType := d.Get("object_type").(string)
115+
privileges := d.Get("privileges").(*schema.Set).List()
116+
117+
// Verify fields that are mandatory for specific object types
118+
if objectType != "database" && d.Get("schema").(string) == "" {
119+
return fmt.Errorf("parameter 'schema' is mandatory for object_type %s", objectType)
120+
}
121+
114122
allowed, ok := allowedPrivileges[objectType]
115123
if !ok {
116124
return fmt.Errorf("unknown object type %s", objectType)

postgresql/resource_postgresql_default_privileges.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ func resourcePostgreSQLDefaultPrivilegesRead(d *schema.ResourceData, meta interf
9393
}
9494

9595
func resourcePostgreSQLDefaultPrivilegesCreate(d *schema.ResourceData, meta interface{}) error {
96-
if err := validatePrivileges(d.Get("object_type").(string), d.Get("privileges").(*schema.Set).List()); err != nil {
96+
if err := validatePrivileges(d); err != nil {
9797
return err
9898
}
9999

@@ -271,7 +271,6 @@ func revokeRoleDefaultPrivileges(txn *sql.Tx, d *schema.ResourceData) error {
271271
return errwrap.Wrapf("could not revoke default privileges: {{err}}", err)
272272
}
273273
return nil
274-
275274
}
276275

277276
func generateDefaultPrivilegesID(d *schema.ResourceData) string {

postgresql/resource_postgresql_grant.go

+133-45
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,12 @@ import (
1414
"github.com/lib/pq"
1515
)
1616

17+
var allowedObjectTypes = []string{
18+
"database",
19+
"table",
20+
"sequence",
21+
}
22+
1723
var objectTypes = map[string]string{
1824
"table": "r",
1925
"sequence": "S",
@@ -42,19 +48,16 @@ func resourcePostgreSQLGrant() *schema.Resource {
4248
},
4349
"schema": {
4450
Type: schema.TypeString,
45-
Required: true,
51+
Optional: true,
4652
ForceNew: true,
4753
Description: "The database schema to grant privileges on for this role",
4854
},
4955
"object_type": {
50-
Type: schema.TypeString,
51-
Required: true,
52-
ForceNew: true,
53-
ValidateFunc: validation.StringInSlice([]string{
54-
"table",
55-
"sequence",
56-
}, false),
57-
Description: "The PostgreSQL object type to grant the privileges on (one of: table, sequence)",
56+
Type: schema.TypeString,
57+
Required: true,
58+
ForceNew: true,
59+
ValidateFunc: validation.StringInSlice(allowedObjectTypes, false),
60+
Description: "The PostgreSQL object type to grant the privileges on (one of: " + strings.Join(allowedObjectTypes, ", ") + ")",
5861
},
5962
"privileges": &schema.Schema{
6063
Type: schema.TypeSet,
@@ -64,6 +67,13 @@ func resourcePostgreSQLGrant() *schema.Resource {
6467
MinItems: 1,
6568
Description: "The list of privileges to grant",
6669
},
70+
"with_grant_option": {
71+
Type: schema.TypeBool,
72+
Optional: true,
73+
ForceNew: true,
74+
Default: false,
75+
Description: "Permit the grant recipient to grant it to others",
76+
},
6777
},
6878
}
6979
}
@@ -110,7 +120,7 @@ func resourcePostgreSQLGrantCreate(d *schema.ResourceData, meta interface{}) err
110120
)
111121
}
112122

113-
if err := validatePrivileges(d.Get("object_type").(string), d.Get("privileges").(*schema.Set).List()); err != nil {
123+
if err := validatePrivileges(d); err != nil {
114124
return err
115125
}
116126

@@ -181,7 +191,38 @@ func resourcePostgreSQLGrantDelete(d *schema.ResourceData, meta interface{}) err
181191
return nil
182192
}
183193

194+
func readDatabaseRolePriviges(txn *sql.Tx, d *schema.ResourceData) error {
195+
query := `
196+
SELECT privilege_type
197+
FROM (
198+
SELECT (aclexplode(datacl)).* FROM pg_database WHERE datname=$1
199+
) as privileges
200+
JOIN pg_roles ON grantee = pg_roles.oid WHERE rolname = $2
201+
`
202+
203+
privileges := []string{}
204+
rows, err := txn.Query(query, d.Get("database"), d.Get("role"))
205+
if err != nil {
206+
return errwrap.Wrapf("could not read database privileges: {{err}}", err)
207+
}
208+
209+
for rows.Next() {
210+
var privilegeType string
211+
if err := rows.Scan(&privilegeType); err != nil {
212+
return errwrap.Wrapf("could not scan database privilege: {{err}}", err)
213+
}
214+
privileges = append(privileges, privilegeType)
215+
}
216+
217+
d.Set("privileges", privileges)
218+
return nil
219+
}
220+
184221
func readRolePrivileges(txn *sql.Tx, d *schema.ResourceData) error {
222+
if d.Get("object_type").(string) == "database" {
223+
return readDatabaseRolePriviges(txn, d)
224+
}
225+
185226
// This returns, for the specified role (rolname),
186227
// the list of all object of the specified type (relkind) in the specified schema (namespace)
187228
// with the list of the currently applied privileges (aggregation of privilege_type)
@@ -236,34 +277,74 @@ GROUP BY pg_class.relname;
236277
return nil
237278
}
238279

280+
func createGrantQuery(d *schema.ResourceData, privileges []string) string {
281+
var query string
282+
283+
switch strings.ToUpper(d.Get("object_type").(string)) {
284+
case "DATABASE":
285+
query = fmt.Sprintf(
286+
"GRANT %s ON DATABASE %s TO %s",
287+
strings.Join(privileges, ","),
288+
pq.QuoteIdentifier(d.Get("database").(string)),
289+
pq.QuoteIdentifier(d.Get("role").(string)),
290+
)
291+
case "TABLE", "SEQUENCE":
292+
query = fmt.Sprintf(
293+
"GRANT %s ON ALL %sS IN SCHEMA %s TO %s",
294+
strings.Join(privileges, ","),
295+
strings.ToUpper(d.Get("object_type").(string)),
296+
pq.QuoteIdentifier(d.Get("schema").(string)),
297+
pq.QuoteIdentifier(d.Get("role").(string)),
298+
)
299+
}
300+
301+
if d.Get("with_grant_option").(bool) == true {
302+
query = query + " WITH GRANT OPTION"
303+
}
304+
305+
return query
306+
}
307+
308+
func createRevokeQuery(d *schema.ResourceData) string {
309+
var query string
310+
311+
switch strings.ToUpper(d.Get("object_type").(string)) {
312+
case "DATABASE":
313+
query = fmt.Sprintf(
314+
"REVOKE ALL PRIVILEGES ON DATABASE %s FROM %s",
315+
pq.QuoteIdentifier(d.Get("database").(string)),
316+
pq.QuoteIdentifier(d.Get("role").(string)),
317+
)
318+
case "TABLE", "SEQUENCE":
319+
query = fmt.Sprintf(
320+
"REVOKE ALL PRIVILEGES ON ALL %sS IN SCHEMA %s FROM %s",
321+
strings.ToUpper(d.Get("object_type").(string)),
322+
pq.QuoteIdentifier(d.Get("schema").(string)),
323+
pq.QuoteIdentifier(d.Get("role").(string)),
324+
)
325+
}
326+
327+
return query
328+
}
329+
239330
func grantRolePrivileges(txn *sql.Tx, d *schema.ResourceData) error {
240331
privileges := []string{}
241332
for _, priv := range d.Get("privileges").(*schema.Set).List() {
242333
privileges = append(privileges, priv.(string))
243334
}
244335

245-
query := fmt.Sprintf(
246-
"GRANT %s ON ALL %sS IN SCHEMA %s TO %s",
247-
strings.Join(privileges, ","),
248-
strings.ToUpper(d.Get("object_type").(string)),
249-
pq.QuoteIdentifier(d.Get("schema").(string)),
250-
pq.QuoteIdentifier(d.Get("role").(string)),
251-
)
336+
query := createGrantQuery(d, privileges)
252337

253338
_, err := txn.Exec(query)
254339
return err
255340
}
256341

257342
func revokeRolePrivileges(txn *sql.Tx, d *schema.ResourceData) error {
258-
query := fmt.Sprintf(
259-
"REVOKE ALL PRIVILEGES ON ALL %sS IN SCHEMA %s FROM %s",
260-
strings.ToUpper(d.Get("object_type").(string)),
261-
pq.QuoteIdentifier(d.Get("schema").(string)),
262-
pq.QuoteIdentifier(d.Get("role").(string)),
263-
)
264-
265-
_, err := txn.Exec(query)
266-
return err
343+
query := createRevokeQuery(d)
344+
if _, err := txn.Exec(query); err != nil {
345+
return errwrap.Wrapf("could not execute revoke query: {{err}}", err)
346+
}
347+
return nil
267348
}
268349

269350
func checkRoleDBSchemaExists(client *Client, d *schema.ResourceData) (bool, error) {
@@ -295,30 +376,37 @@ func checkRoleDBSchemaExists(client *Client, d *schema.ResourceData) (bool, erro
295376
return false, nil
296377
}
297378

298-
// Connect on this database to check if schema exists
299-
dbTxn, err := startTransaction(client, database)
300-
if err != nil {
301-
return false, err
302-
}
303-
defer dbTxn.Rollback()
379+
if d.Get("object_type").(string) != "database" {
380+
// Connect on this database to check if schema exists
381+
dbTxn, err := startTransaction(client, database)
382+
if err != nil {
383+
return false, err
384+
}
385+
defer dbTxn.Rollback()
304386

305-
// Check the schema exists (the SQL connection needs to be on the right database)
306-
pgSchema := d.Get("schema").(string)
307-
exists, err = schemaExists(dbTxn, pgSchema)
308-
if err != nil {
309-
return false, err
310-
}
311-
if !exists {
312-
log.Printf("[DEBUG] schema %s does not exists", pgSchema)
313-
return false, nil
387+
// Check the schema exists (the SQL connection needs to be on the right database)
388+
pgSchema := d.Get("schema").(string)
389+
exists, err = schemaExists(dbTxn, pgSchema)
390+
if err != nil {
391+
return false, err
392+
}
393+
if !exists {
394+
log.Printf("[DEBUG] schema %s does not exists", pgSchema)
395+
return false, nil
396+
}
314397
}
315398

316399
return true, nil
317400
}
318401

319402
func generateGrantID(d *schema.ResourceData) string {
320-
return strings.Join([]string{
321-
d.Get("role").(string), d.Get("database").(string),
322-
d.Get("schema").(string), d.Get("object_type").(string),
323-
}, "_")
403+
parts := []string{d.Get("role").(string), d.Get("database").(string)}
404+
405+
objectType := d.Get("object_type").(string)
406+
if objectType != "database" {
407+
parts = append(parts, d.Get("schema").(string))
408+
}
409+
parts = append(parts, objectType)
410+
411+
return strings.Join(parts, "_")
324412
}

0 commit comments

Comments
 (0)