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

Commit

Permalink
New resource: posgresql_default_privileges
Browse files Browse the repository at this point in the history
This resource allow to manage default privileges for tables or sequences for a specified role in a schema.
  • Loading branch information
cyrilgdn committed Mar 4, 2019
1 parent 218c7f4 commit e8dfd9f
Show file tree
Hide file tree
Showing 6 changed files with 366 additions and 52 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ FEATURES:

* New resource: postgresql_grant. This resource allows to grant privileges on all existing tables or sequences for a specified role in a specified schema.
([#51](https://github.com/terraform-providers/terraform-provider-postgresql/pull/51))
* New resource: postgresql_default_privileges. This resource allow to manage default privileges for tables or sequences for a specified role in a specified schema.
([#53](https://github.com/terraform-providers/terraform-provider-postgresql/pull/53))

## 0.2.1 (February 28, 2019)

Expand Down
11 changes: 6 additions & 5 deletions postgresql/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,12 @@ func Provider() terraform.ResourceProvider {
},

ResourcesMap: map[string]*schema.Resource{
"postgresql_database": resourcePostgreSQLDatabase(),
"postgresql_extension": resourcePostgreSQLExtension(),
"postgresql_schema": resourcePostgreSQLSchema(),
"postgresql_role": resourcePostgreSQLRole(),
"postgresql_grant": resourcePostgreSQLGrant(),
"postgresql_database": resourcePostgreSQLDatabase(),
"postgresql_default_privileges": resourcePostgreSQLDefaultPrivileges(),
"postgresql_extension": resourcePostgreSQLExtension(),
"postgresql_grant": resourcePostgreSQLGrant(),
"postgresql_schema": resourcePostgreSQLSchema(),
"postgresql_role": resourcePostgreSQLRole(),
},

ConfigureFunc: providerConfigure,
Expand Down
248 changes: 248 additions & 0 deletions postgresql/resource_postgresql_default_privileges.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
package postgresql

import (
"database/sql"
"fmt"
"log"
"strings"

"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/helper/validation"

// Use Postgres as SQL driver
"github.com/lib/pq"
)

func resourcePostgreSQLDefaultPrivileges() *schema.Resource {
return &schema.Resource{
Create: resourcePostgreSQLDefaultPrivilegesCreate,
Update: resourcePostgreSQLDefaultPrivilegesCreate,
Read: resourcePostgreSQLDefaultPrivilegesRead,
Delete: resourcePostgreSQLDefaultPrivilegesDelete,

Schema: map[string]*schema.Schema{
"role": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "The name of the role to which grant default privileges on",
},
"database": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "The database to grant default privileges for this role",
},
"owner": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "Role for which apply default privileges (You can change default privileges only for objects that will be created by yourself or by roles that you are a member of)",
},
"schema": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
Description: "The database schema to set default privileges for this role",
},
"object_type": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
ValidateFunc: validation.StringInSlice([]string{
"table",
"sequence",
}, false),
Description: "The PostgreSQL object type to set the default privileges on (one of: table, sequence)",
},
"privileges": &schema.Schema{
Type: schema.TypeSet,
Required: true,
Elem: &schema.Schema{Type: schema.TypeString},
Set: schema.HashString,
MinItems: 1,
Description: "The list of privileges to apply as default privileges",
},
},
}
}

func resourcePostgreSQLDefaultPrivilegesRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Client)

client.catalogLock.RLock()
defer client.catalogLock.RUnlock()

exists, err := checkRoleDBSchemaExists(client, d)
if err != nil {
return err
}
if !exists {
d.SetId("")
return nil
}

txn, err := startTransaction(client, d.Get("database").(string))
if err != nil {
return err
}
defer deferredRollback(txn)

return readRoleDefaultPrivileges(txn, d)
}

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

database := d.Get("database").(string)

client := meta.(*Client)

client.catalogLock.Lock()
defer client.catalogLock.Unlock()

txn, err := startTransaction(client, database)
if err != nil {
return err
}
defer deferredRollback(txn)

// Revoke all privileges before granting otherwise reducing privileges will not work.
// We just have to revoke them in the same transaction so role will not lost his privileges between revoke and grant.
if err = revokeRoleDefaultPrivileges(txn, d); err != nil {
return err
}

if err = grantRoleDefaultPrivileges(txn, d); err != nil {
return err
}

if err := txn.Commit(); err != nil {
return err
}

d.SetId(generateDefaultPrivilegesID(d))

txn, err = startTransaction(client, d.Get("database").(string))
if err != nil {
return err
}
defer deferredRollback(txn)

return readRoleDefaultPrivileges(txn, d)
}

func resourcePostgreSQLDefaultPrivilegesDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Client)

client.catalogLock.Lock()
defer client.catalogLock.Unlock()

txn, err := startTransaction(client, d.Get("database").(string))
if err != nil {
return err
}
defer deferredRollback(txn)

revokeRoleDefaultPrivileges(txn, d)
if err := txn.Commit(); err != nil {
return err
}

return nil
}

func readRoleDefaultPrivileges(txn *sql.Tx, d *schema.ResourceData) error {
role := d.Get("role").(string)
owner := d.Get("owner").(string)
pgSchema := d.Get("schema").(string)
objectType := d.Get("object_type").(string)

// This query aggregates the list of default privileges type (prtype)
// for the role (grantee), owner (grantor), schema (namespace name)
// and the specified object type (defaclobjtype).
query := `SELECT array_agg(prtype) FROM (
SELECT defaclnamespace, (aclexplode(defaclacl)).* FROM pg_default_acl
WHERE defaclobjtype = $3
) AS t (namespace, grantor_oid, grantee_oid, prtype, grantable)
JOIN pg_namespace ON pg_namespace.oid = namespace
WHERE pg_get_userbyid(grantee_oid) = $1 AND nspname = $2 AND pg_get_userbyid(grantor_oid) = $4;
`
var privileges pq.ByteaArray

if err := txn.QueryRow(
query, role, pgSchema, objectTypes[objectType], owner,
).Scan(&privileges); err != nil {
return errwrap.Wrapf("could not read default privileges: {{err}}", err)
}

// We consider no privileges as "not exists"
if len(privileges) == 0 {
log.Printf("[DEBUG] no default privileges for role %s in schema %s", role, pgSchema)
d.SetId("")
return nil
}

privilegesSet := pgArrayToSet(privileges)
d.Set("privileges", privilegesSet)
d.SetId(generateDefaultPrivilegesID(d))

return nil
}

func grantRoleDefaultPrivileges(txn *sql.Tx, d *schema.ResourceData) error {
role := d.Get("role").(string)
pgSchema := d.Get("schema").(string)

privileges := []string{}
for _, priv := range d.Get("privileges").(*schema.Set).List() {
privileges = append(privileges, priv.(string))
}

// TODO: We grant default privileges for the DB owner
// For that we need to be either superuser or a member of the owner role.
// With AWS RDS, It's not possible to create superusers as it is restricted by AWS itself.
// In that case, the only solution would be to have the PostgreSQL user used by Terraform
// to be also part of the database owner role.

query := fmt.Sprintf("ALTER DEFAULT PRIVILEGES FOR ROLE %s IN SCHEMA %s GRANT %s ON %sS TO %s",
pq.QuoteIdentifier(d.Get("owner").(string)),
pq.QuoteIdentifier(pgSchema),
strings.Join(privileges, ","),
strings.ToUpper(d.Get("object_type").(string)),
pq.QuoteIdentifier(role),
)

_, err := txn.Exec(
query,
)
if err != nil {
return errwrap.Wrapf("could not alter default privileges: {{err}}", err)
}

return nil
}

func revokeRoleDefaultPrivileges(txn *sql.Tx, d *schema.ResourceData) error {
query := fmt.Sprintf(
"ALTER DEFAULT PRIVILEGES FOR ROLE %s IN SCHEMA %s REVOKE ALL ON %sS FROM %s",
pq.QuoteIdentifier(d.Get("owner").(string)),
pq.QuoteIdentifier(d.Get("schema").(string)),
strings.ToUpper(d.Get("object_type").(string)),
pq.QuoteIdentifier(d.Get("role").(string)),
)

_, err := txn.Exec(query)
return err
}

func generateDefaultPrivilegesID(d *schema.ResourceData) string {
return strings.Join([]string{
d.Get("role").(string), d.Get("database").(string), d.Get("schema").(string),
d.Get("owner").(string), d.Get("object_type").(string),
}, "_")
}
56 changes: 56 additions & 0 deletions postgresql/resource_postgresql_default_privileges_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package postgresql

import (
"fmt"
"testing"

"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)

func TestAccPostgresqlDefaultPrivileges(t *testing.T) {
// We have to create the database outside of resource.Test
// because we need to create a table to assert that grant are correctly applied
// and we don't have this resource yet
dbSuffix, teardown := setupTestDatabase(t, true, true)
defer teardown()

config := getTestConfig(t)
dbName, roleName := getTestDBNames(dbSuffix)

// We set PGUSER as owner as he will create the test table
var testDPSelect = fmt.Sprintf(`
resource "postgresql_default_privileges" "test_ro" {
database = "%s"
owner = "%s"
role = "%s"
schema = "public"
object_type = "table"
privileges = ["SELECT"]
}
`, dbName, config.Username, roleName)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testDPSelect,
Check: resource.ComposeTestCheckFunc(
func(*terraform.State) error {
tables := []string{"test_table"}
// To test default privileges, we need to create a table
// after having apply the state.
dropFunc := createTestTables(t, dbSuffix, tables)
defer dropFunc()

return testCheckTablesPrivileges(t, dbSuffix, tables, []string{"SELECT"})
},
resource.TestCheckResourceAttr("postgresql_default_privileges.test_ro", "object_type", "table"),
resource.TestCheckResourceAttr("postgresql_default_privileges.test_ro", "privileges.#", "1"),
resource.TestCheckResourceAttr("postgresql_default_privileges.test_ro", "privileges.3138006342", "SELECT"),
),
},
},
})
}
19 changes: 11 additions & 8 deletions postgresql/resource_postgresql_grant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ import (

func TestAccPostgresqlGrant(t *testing.T) {
// We have to create the database outside of resource.Test
// because we need to create a table to assert that grant are correctly applied
// because we need to create tables to assert that grant are correctly applied
// and we don't have this resource yet
dbSuffix, teardown := setupTestDatabase(t, true, true, true)
dbSuffix, teardown := setupTestDatabase(t, true, true)
defer teardown()

testTables := []string{"test_table", "test_table2"}
createTestTables(t, dbSuffix, testTables)

dbName, roleName := getTestDBNames(dbSuffix)
var testGrantSelect = fmt.Sprintf(`
resource "postgresql_grant" "test_ro" {
Expand Down Expand Up @@ -43,23 +46,23 @@ func TestAccPostgresqlGrant(t *testing.T) {
{
Config: testGrantSelect,
Check: resource.ComposeTestCheckFunc(
func(*terraform.State) error {
return testCheckTablePrivileges(t, dbSuffix, []string{"SELECT"}, false)
},
resource.TestCheckResourceAttr("postgresql_grant.test_ro", "privileges.#", "1"),
resource.TestCheckResourceAttr("postgresql_grant.test_ro", "privileges.3138006342", "SELECT"),
func(*terraform.State) error {
return testCheckTablesPrivileges(t, dbSuffix, testTables, []string{"SELECT"})
},
),
},
{
Config: testGrantSelectInsertUpdate,
Check: resource.ComposeTestCheckFunc(
func(*terraform.State) error {
return testCheckTablePrivileges(t, dbSuffix, []string{"SELECT", "INSERT", "UPDATE"}, false)
},
resource.TestCheckResourceAttr("postgresql_grant.test_ro", "privileges.#", "3"),
resource.TestCheckResourceAttr("postgresql_grant.test_ro", "privileges.3138006342", "SELECT"),
resource.TestCheckResourceAttr("postgresql_grant.test_ro", "privileges.892623219", "INSERT"),
resource.TestCheckResourceAttr("postgresql_grant.test_ro", "privileges.1759376126", "UPDATE"),
func(*terraform.State) error {
return testCheckTablesPrivileges(t, dbSuffix, testTables, []string{"SELECT", "INSERT", "UPDATE"})
},
),
},
},
Expand Down
Loading

0 comments on commit e8dfd9f

Please sign in to comment.