Add the postgresql_schema_policy resource. This is a WIP due to
issues with cycles when an object is destroyed.
This commit is contained in:
parent
de6dcbd8cd
commit
ebc81727da
|
@ -64,10 +64,11 @@ func Provider() terraform.ResourceProvider {
|
||||||
},
|
},
|
||||||
|
|
||||||
ResourcesMap: map[string]*schema.Resource{
|
ResourcesMap: map[string]*schema.Resource{
|
||||||
"postgresql_database": resourcePostgreSQLDatabase(),
|
"postgresql_database": resourcePostgreSQLDatabase(),
|
||||||
"postgresql_extension": resourcePostgreSQLExtension(),
|
"postgresql_extension": resourcePostgreSQLExtension(),
|
||||||
"postgresql_schema": resourcePostgreSQLSchema(),
|
"postgresql_schema": resourcePostgreSQLSchema(),
|
||||||
"postgresql_role": resourcePostgreSQLRole(),
|
"postgresql_schema_policy": resourcePostgreSQLSchemaPolicy(),
|
||||||
|
"postgresql_role": resourcePostgreSQLRole(),
|
||||||
},
|
},
|
||||||
|
|
||||||
ConfigureFunc: providerConfigure,
|
ConfigureFunc: providerConfigure,
|
||||||
|
|
|
@ -0,0 +1,442 @@
|
||||||
|
package postgresql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/hashicorp/errwrap"
|
||||||
|
uuid "github.com/hashicorp/go-uuid"
|
||||||
|
"github.com/hashicorp/terraform/helper/schema"
|
||||||
|
"github.com/lib/pq"
|
||||||
|
"github.com/sean-/pgacl"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
schemaPolicyCreateAttr = "create"
|
||||||
|
schemaPolicyCreateWithGrantAttr = "create_with_grant"
|
||||||
|
schemaPolicyRoleAttr = "role"
|
||||||
|
schemaPolicySchemaAttr = "schema"
|
||||||
|
schemaPolicyUsageAttr = "usage"
|
||||||
|
schemaPolicyUsageWithGrantAttr = "usage_with_grant"
|
||||||
|
// schemaPolicyRolesAttr = "roles"
|
||||||
|
// schemaPolicySchemasAttr = "schemas"
|
||||||
|
)
|
||||||
|
|
||||||
|
func resourcePostgreSQLSchemaPolicy() *schema.Resource {
|
||||||
|
return &schema.Resource{
|
||||||
|
Create: resourcePostgreSQLSchemaPolicyCreate,
|
||||||
|
Read: resourcePostgreSQLSchemaPolicyRead,
|
||||||
|
Update: resourcePostgreSQLSchemaPolicyUpdate,
|
||||||
|
Delete: resourcePostgreSQLSchemaPolicyDelete,
|
||||||
|
Importer: &schema.ResourceImporter{
|
||||||
|
State: schema.ImportStatePassthrough,
|
||||||
|
},
|
||||||
|
|
||||||
|
Schema: map[string]*schema.Schema{
|
||||||
|
schemaPolicyCreateAttr: {
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
Default: false,
|
||||||
|
Description: "If true, allow the specified ROLEs to CREATE new objects within the schema(s)",
|
||||||
|
ConflictsWith: []string{schemaPolicyCreateWithGrantAttr},
|
||||||
|
},
|
||||||
|
schemaPolicyCreateWithGrantAttr: {
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
Default: false,
|
||||||
|
Description: "If true, allow the specified ROLEs to CREATE new objects within the schema(s) and GRANT the same CREATE privilege to different ROLEs",
|
||||||
|
ConflictsWith: []string{schemaPolicyCreateAttr},
|
||||||
|
},
|
||||||
|
// schemaPolicyRolesAttr: {
|
||||||
|
// Type: schema.TypeList,
|
||||||
|
// Elem: &schema.Schema{Type: schema.TypeString},
|
||||||
|
// Description: "List of ROLEs who will receive this policy",
|
||||||
|
// },
|
||||||
|
schemaPolicyRoleAttr: {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
Description: "ROLE who will receive this policy",
|
||||||
|
},
|
||||||
|
schemaPolicySchemaAttr: {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Required: true,
|
||||||
|
Description: "Target SCHEMA who will have this policy applied to it",
|
||||||
|
},
|
||||||
|
schemaPolicyUsageAttr: {
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
Default: false,
|
||||||
|
Description: "If true, allow the specified ROLEs to use objects within the schema(s)",
|
||||||
|
ConflictsWith: []string{schemaPolicyUsageWithGrantAttr},
|
||||||
|
},
|
||||||
|
schemaPolicyUsageWithGrantAttr: {
|
||||||
|
Type: schema.TypeBool,
|
||||||
|
Optional: true,
|
||||||
|
Default: false,
|
||||||
|
Description: "If true, allow the specified ROLEs to use objects within the schema(s) and GRANT the same USAGE privilege to different ROLEs",
|
||||||
|
ConflictsWith: []string{schemaPolicyUsageAttr},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourcePostgreSQLSchemaPolicyCreate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
id, err := uuid.GenerateUUID()
|
||||||
|
if err != nil {
|
||||||
|
return errwrap.Wrapf("Unable to generate schema policy ID: {{err}}", err)
|
||||||
|
}
|
||||||
|
d.SetId(id)
|
||||||
|
|
||||||
|
acl := pgacl.Schema{}
|
||||||
|
role := d.Get(schemaPolicyRoleAttr).(string)
|
||||||
|
if strings.ToUpper(role) != "PUBLIC" {
|
||||||
|
role = pq.QuoteIdentifier(role)
|
||||||
|
}
|
||||||
|
|
||||||
|
acl.CreateGrant = d.Get(schemaPolicyCreateWithGrantAttr).(bool)
|
||||||
|
if acl.CreateGrant {
|
||||||
|
acl.Create = true
|
||||||
|
} else {
|
||||||
|
acl.Create = d.Get(schemaPolicyCreateAttr).(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
acl.UsageGrant = d.Get(schemaPolicyUsageWithGrantAttr).(bool)
|
||||||
|
if acl.UsageGrant {
|
||||||
|
acl.Usage = true
|
||||||
|
} else {
|
||||||
|
acl.Usage = d.Get(schemaPolicyUsageAttr).(bool)
|
||||||
|
}
|
||||||
|
schema := d.Get(schemaPolicySchemaAttr).(string)
|
||||||
|
|
||||||
|
queries := make([]string, 0, 2)
|
||||||
|
if acl.Create {
|
||||||
|
b := bytes.NewBufferString("GRANT CREATE ON SCHEMA ")
|
||||||
|
fmt.Fprintf(b, "%s TO %s", pq.QuoteIdentifier(schema), role)
|
||||||
|
|
||||||
|
if acl.CreateGrant {
|
||||||
|
fmt.Fprint(b, " WITH GRANT OPTION")
|
||||||
|
}
|
||||||
|
queries = append(queries, b.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
if acl.Usage {
|
||||||
|
b := bytes.NewBufferString("GRANT USAGE ON SCHEMA ")
|
||||||
|
fmt.Fprintf(b, "%s TO %s", pq.QuoteIdentifier(schema), role)
|
||||||
|
if acl.UsageGrant {
|
||||||
|
fmt.Fprint(b, " WITH GRANT OPTION")
|
||||||
|
}
|
||||||
|
queries = append(queries, b.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
c := meta.(*Client)
|
||||||
|
conn, err := c.Connect()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
txn, err := conn.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer txn.Rollback()
|
||||||
|
|
||||||
|
for _, query := range queries {
|
||||||
|
_, err = txn.Query(query)
|
||||||
|
if err != nil {
|
||||||
|
return errwrap.Wrapf(fmt.Sprintf("Error applying policy on schema (%+q): {{err}}", query), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := txn.Commit(); err != nil {
|
||||||
|
return errwrap.Wrapf("Error committing schema policy: {{err}}", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourcePostgreSQLSchemaPolicyRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourcePostgreSQLSchemaPolicyDelete(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
role := d.Get(schemaPolicyRoleAttr).(string)
|
||||||
|
schema := d.Get(schemaPolicySchemaAttr).(string)
|
||||||
|
|
||||||
|
acl := pgacl.Schema{}
|
||||||
|
if strings.ToUpper(role) != "PUBLIC" {
|
||||||
|
role = pq.QuoteIdentifier(role)
|
||||||
|
}
|
||||||
|
|
||||||
|
acl.CreateGrant = d.Get(schemaPolicyCreateWithGrantAttr).(bool)
|
||||||
|
if acl.CreateGrant {
|
||||||
|
acl.Create = true
|
||||||
|
} else {
|
||||||
|
acl.Create = d.Get(schemaPolicyCreateAttr).(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
acl.UsageGrant = d.Get(schemaPolicyUsageWithGrantAttr).(bool)
|
||||||
|
if acl.UsageGrant {
|
||||||
|
acl.Usage = true
|
||||||
|
} else {
|
||||||
|
acl.Usage = d.Get(schemaPolicyUsageAttr).(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
queries := make([]string, 0, 2)
|
||||||
|
switch {
|
||||||
|
case !acl.Create:
|
||||||
|
b := bytes.NewBufferString("REVOKE CREATE ON SCHEMA ")
|
||||||
|
fmt.Fprintf(b, "%s FROM %s", pq.QuoteIdentifier(schema), role)
|
||||||
|
queries = append(queries, b.String())
|
||||||
|
case acl.Create && !acl.CreateGrant:
|
||||||
|
b := bytes.NewBufferString("REVOKE GRANT OPTION FOR CREATE ON SCHEMA ")
|
||||||
|
fmt.Fprintf(b, "%s FROM %s", pq.QuoteIdentifier(schema), role)
|
||||||
|
queries = append(queries, b.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case !acl.Usage:
|
||||||
|
b := bytes.NewBufferString("REVOKE USAGE ON SCHEMA ")
|
||||||
|
fmt.Fprintf(b, "%s FROM %s", pq.QuoteIdentifier(schema), role)
|
||||||
|
queries = append(queries, b.String())
|
||||||
|
case acl.Usage && !acl.UsageGrant:
|
||||||
|
b := bytes.NewBufferString("REVOKE GRANT OPTION FOR USAGE ON SCHEMA ")
|
||||||
|
fmt.Fprintf(b, "%s FROM %s", pq.QuoteIdentifier(schema), role)
|
||||||
|
queries = append(queries, b.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
c := meta.(*Client)
|
||||||
|
conn, err := c.Connect()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
txn, err := conn.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer txn.Rollback()
|
||||||
|
|
||||||
|
for _, query := range queries {
|
||||||
|
_, err = txn.Query(query)
|
||||||
|
if err != nil {
|
||||||
|
return errwrap.Wrapf(fmt.Sprintf("Error removing policy on schema (%+q): {{err}}", query), err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
txn.Commit()
|
||||||
|
|
||||||
|
d.SetId("")
|
||||||
|
|
||||||
|
return resourcePostgreSQLSchemaPolicyRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourcePostgreSQLSchemaPolicyRead(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
c := meta.(*Client)
|
||||||
|
conn, err := c.Connect()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
oraw, _ := d.GetChange(schemaPolicySchemaAttr)
|
||||||
|
o := oraw.(string)
|
||||||
|
|
||||||
|
var schemaName, schemaOwner, schemaACL string
|
||||||
|
err = conn.QueryRow("SELECT n.nspname, pg_catalog.pg_get_userbyid(n.nspowner), n.nspacl FROM pg_catalog.pg_namespace n WHERE n.nspname = $1", o).Scan(&schemaName, &schemaOwner, &schemaACL)
|
||||||
|
switch {
|
||||||
|
case err == sql.ErrNoRows:
|
||||||
|
log.Printf("[WARN] PostgreSQL schema (%s) not found", o)
|
||||||
|
d.SetId("")
|
||||||
|
return nil
|
||||||
|
case err != nil:
|
||||||
|
return errwrap.Wrapf("Error reading schema ACLs: {{err}}", err)
|
||||||
|
default:
|
||||||
|
acl, err := pgacl.NewSchema(schemaACL)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch {
|
||||||
|
case acl.CreateGrant:
|
||||||
|
d.Set(schemaPolicyCreateWithGrantAttr, true)
|
||||||
|
d.Set(schemaPolicyCreateAttr, false)
|
||||||
|
case acl.Create:
|
||||||
|
d.Set(schemaPolicyCreateAttr, true)
|
||||||
|
d.Set(schemaPolicyCreateWithGrantAttr, false)
|
||||||
|
default:
|
||||||
|
d.Set(schemaPolicyCreateWithGrantAttr, false)
|
||||||
|
d.Set(schemaPolicyCreateAttr, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case acl.UsageGrant:
|
||||||
|
d.Set(schemaPolicyUsageWithGrantAttr, true)
|
||||||
|
d.Set(schemaPolicyUsageAttr, false)
|
||||||
|
case acl.Usage:
|
||||||
|
d.Set(schemaPolicyUsageAttr, true)
|
||||||
|
d.Set(schemaPolicyUsageWithGrantAttr, false)
|
||||||
|
default:
|
||||||
|
d.Set(schemaPolicyUsageWithGrantAttr, false)
|
||||||
|
d.Set(schemaPolicyUsageAttr, false)
|
||||||
|
}
|
||||||
|
d.Set(schemaPolicySchemaAttr, acl.Role)
|
||||||
|
d.Set(schemaPolicySchemaAttr, schemaName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resourcePostgreSQLSchemaPolicyUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||||
|
c := meta.(*Client)
|
||||||
|
conn, err := c.Connect()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
txn, err := conn.Begin()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer txn.Rollback()
|
||||||
|
|
||||||
|
if err := setSchemaPolicyCreate(txn, d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := setSchemaPolicyUsage(txn, d); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
txn.Commit()
|
||||||
|
|
||||||
|
return resourcePostgreSQLSchemaRead(d, meta)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setSchemaPolicyCreate(txn *sql.Tx, d *schema.ResourceData) error {
|
||||||
|
if !d.HasChange(schemaPolicyCreateAttr) && !d.HasChange(schemaPolicyCreateWithGrantAttr) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
oldCreateRaw, newCreateRaw := d.GetChange(schemaPolicyCreateAttr)
|
||||||
|
oldCreate := oldCreateRaw.(bool)
|
||||||
|
newCreate := newCreateRaw.(bool)
|
||||||
|
|
||||||
|
oldGrantRaw, newGrantRaw := d.GetChange(schemaPolicyCreateWithGrantAttr)
|
||||||
|
oldGrant := oldGrantRaw.(bool)
|
||||||
|
newGrant := newGrantRaw.(bool)
|
||||||
|
|
||||||
|
var grant, revoke, withGrant bool
|
||||||
|
switch {
|
||||||
|
case oldCreate == newCreate:
|
||||||
|
// nothing changed
|
||||||
|
case oldCreate && !newCreate:
|
||||||
|
// Lost create privs
|
||||||
|
revoke = true
|
||||||
|
case !oldCreate && newCreate:
|
||||||
|
// Gaining create privs
|
||||||
|
grant = true
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case newGrant == oldGrant:
|
||||||
|
// Nothing changed
|
||||||
|
case newGrant && !oldGrant, // Getting WITH GRANT OPTION priv
|
||||||
|
!newGrant && oldGrant: // Loosing WITH GRANT OPTION priv
|
||||||
|
withGrant = true
|
||||||
|
}
|
||||||
|
|
||||||
|
role := d.Get(schemaPolicyRoleAttr).(string)
|
||||||
|
if strings.ToUpper(role) != "PUBLIC" {
|
||||||
|
role = pq.QuoteIdentifier(role)
|
||||||
|
}
|
||||||
|
|
||||||
|
schema := d.Get(schemaPolicySchemaAttr).(string)
|
||||||
|
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
switch {
|
||||||
|
case grant:
|
||||||
|
b = bytes.NewBufferString("GRANT CREATE ON SCHEMA ")
|
||||||
|
fmt.Fprintf(b, "%s TO %s", pq.QuoteIdentifier(schema), role)
|
||||||
|
if withGrant {
|
||||||
|
fmt.Fprint(b, " WITH GRANT OPTION")
|
||||||
|
}
|
||||||
|
case revoke:
|
||||||
|
b = bytes.NewBufferString("REVOKE")
|
||||||
|
if withGrant {
|
||||||
|
fmt.Fprint(b, " GRANT OPTION FOR")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(b, " CREATE ON SCHEMA %s FROM %s", pq.QuoteIdentifier(schema), role)
|
||||||
|
}
|
||||||
|
|
||||||
|
query := b.String()
|
||||||
|
if _, err := txn.Query(query); err != nil {
|
||||||
|
return errwrap.Wrapf("Error updating schema create privileges: {{err}}", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setSchemaPolicyUsage(txn *sql.Tx, d *schema.ResourceData) error {
|
||||||
|
if !d.HasChange(schemaPolicyUsageAttr) && !d.HasChange(schemaPolicyUsageWithGrantAttr) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
oldUsageRaw, newUsageRaw := d.GetChange(schemaPolicyUsageAttr)
|
||||||
|
oldUsage := oldUsageRaw.(bool)
|
||||||
|
newUsage := newUsageRaw.(bool)
|
||||||
|
|
||||||
|
oldGrantRaw, newGrantRaw := d.GetChange(schemaPolicyUsageWithGrantAttr)
|
||||||
|
oldGrant := oldGrantRaw.(bool)
|
||||||
|
newGrant := newGrantRaw.(bool)
|
||||||
|
|
||||||
|
var grant, revoke, withGrant bool
|
||||||
|
switch {
|
||||||
|
case oldUsage == newUsage:
|
||||||
|
// nothing changed
|
||||||
|
case oldUsage && !newUsage:
|
||||||
|
// Lost usage privs
|
||||||
|
revoke = true
|
||||||
|
case !oldUsage && newUsage:
|
||||||
|
// Gaining usage privs
|
||||||
|
grant = true
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case newGrant == oldGrant:
|
||||||
|
// Nothing changed
|
||||||
|
case newGrant && !oldGrant, // Getting WITH GRANT OPTION priv
|
||||||
|
!newGrant && oldGrant: // Loosing WITH GRANT OPTION priv
|
||||||
|
withGrant = true
|
||||||
|
}
|
||||||
|
|
||||||
|
role := d.Get(schemaPolicyRoleAttr).(string)
|
||||||
|
if strings.ToUpper(role) != "PUBLIC" {
|
||||||
|
role = pq.QuoteIdentifier(role)
|
||||||
|
}
|
||||||
|
|
||||||
|
schema := d.Get(schemaPolicySchemaAttr).(string)
|
||||||
|
|
||||||
|
b := &bytes.Buffer{}
|
||||||
|
switch {
|
||||||
|
case grant:
|
||||||
|
b = bytes.NewBufferString("GRANT USAGE ON SCHEMA ")
|
||||||
|
fmt.Fprintf(b, "%s TO %s", pq.QuoteIdentifier(schema), role)
|
||||||
|
if withGrant {
|
||||||
|
fmt.Fprint(b, " WITH GRANT OPTION")
|
||||||
|
}
|
||||||
|
case revoke:
|
||||||
|
b = bytes.NewBufferString("REVOKE")
|
||||||
|
if withGrant {
|
||||||
|
fmt.Fprint(b, " GRANT OPTION FOR")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(b, " USAGE ON SCHEMA %s FROM %s", pq.QuoteIdentifier(schema), role)
|
||||||
|
}
|
||||||
|
|
||||||
|
query := b.String()
|
||||||
|
if _, err := txn.Query(query); err != nil {
|
||||||
|
return errwrap.Wrapf("Error updating schema usage privileges: {{err}}", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,159 @@
|
||||||
|
package postgresql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/helper/resource"
|
||||||
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAccPostgreSQLSchemaPolicy_Basic(t *testing.T) {
|
||||||
|
resource.Test(t, resource.TestCase{
|
||||||
|
PreCheck: func() { testAccPreCheck(t) },
|
||||||
|
Providers: testAccProviders,
|
||||||
|
CheckDestroy: testAccCheckPostgreSQLSchemaPolicyDestroy,
|
||||||
|
Steps: []resource.TestStep{
|
||||||
|
{
|
||||||
|
Config: testAccPostgreSQLSchemaPolicyConfig,
|
||||||
|
Check: resource.ComposeTestCheckFunc(
|
||||||
|
testAccCheckPostgreSQLSchemaPolicyExists("postgresql_schema.test1", "foo"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"postgresql_role.dba", "name", "dba"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"postgresql_role.app1", "name", "app1"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"postgresql_role.app2", "name", "app2"),
|
||||||
|
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"postgresql_schema.foo", "name", "foo"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"postgresql_schema.owner", "name", "dba"),
|
||||||
|
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"postgresql_schema_policy.foo_allow", "create", "false"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"postgresql_schema_policy.foo_allow", "create_with_grant", "true"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"postgresql_schema_policy.foo_allow", "usage", "false"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"postgresql_schema_policy.foo_allow", "usage_with_grant", "true"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"postgresql_schema_policy.foo_allow", "schema", "foo"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"postgresql_schema_policy.foo_allow", "role", "app1"),
|
||||||
|
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"postgresql_schema_policy.foo_deny", "schema", "foo"),
|
||||||
|
resource.TestCheckResourceAttr(
|
||||||
|
"postgresql_schema_policy.foo_deny", "role", "app2"),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckPostgreSQLSchemaPolicyDestroy(s *terraform.State) error {
|
||||||
|
client := testAccProvider.Meta().(*Client)
|
||||||
|
|
||||||
|
for _, rs := range s.RootModule().Resources {
|
||||||
|
if rs.Type != "postgresql_schema" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
exists, err := checkSchemaExists(client, rs.Primary.ID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error checking schema %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
return fmt.Errorf("Schema still exists after destroy")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testAccCheckPostgreSQLSchemaPolicyExists(n string, schemaName string) resource.TestCheckFunc {
|
||||||
|
return func(s *terraform.State) error {
|
||||||
|
rs, ok := s.RootModule().Resources[n]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("Resource not found: %s", n)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rs.Primary.ID == "" {
|
||||||
|
return fmt.Errorf("No ID is set")
|
||||||
|
}
|
||||||
|
|
||||||
|
actualSchemaName := rs.Primary.Attributes["name"]
|
||||||
|
if actualSchemaName != schemaName {
|
||||||
|
return fmt.Errorf("Wrong value for schema name expected %s got %s", schemaName, actualSchemaName)
|
||||||
|
}
|
||||||
|
|
||||||
|
client := testAccProvider.Meta().(*Client)
|
||||||
|
exists, err := checkSchemaExists(client, rs.Primary.ID)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Error checking schema %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("Schema not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkSchemaPolicyExists(client *Client, schemaName string) (bool, error) {
|
||||||
|
conn, err := client.Connect()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
var _rez string
|
||||||
|
err = conn.QueryRow("SELECT nspname FROM pg_catalog.pg_namespace WHERE nspname=$1", schemaName).Scan(&_rez)
|
||||||
|
switch {
|
||||||
|
case err == sql.ErrNoRows:
|
||||||
|
return false, nil
|
||||||
|
case err != nil:
|
||||||
|
return false, fmt.Errorf("Error reading info about schema: %s", err)
|
||||||
|
default:
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var testAccPostgreSQLSchemaPolicyConfig = `
|
||||||
|
resource "postgresql_role" "dba" {
|
||||||
|
name = "dba"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "postgresql_role" "app1" {
|
||||||
|
name = "app1"
|
||||||
|
# depends_on = ["postgresql_schema_policy.foo_allow"]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "postgresql_role" "app2" {
|
||||||
|
name = "app2"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "postgresql_schema" "foo" {
|
||||||
|
name = "foo"
|
||||||
|
owner = "${postgresql_role.dba.name}"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "postgresql_schema_policy" "foo_allow" {
|
||||||
|
create_with_grant = true
|
||||||
|
usage_with_grant = true
|
||||||
|
|
||||||
|
schema = "${postgresql_schema.foo.name}"
|
||||||
|
role = "${postgresql_role.app1.name}"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "postgresql_schema_policy" "foo_deny" {
|
||||||
|
schema = "${postgresql_schema.foo.name}"
|
||||||
|
role = "${postgresql_role.app2.name}"
|
||||||
|
}
|
||||||
|
`
|
|
@ -0,0 +1,25 @@
|
||||||
|
BSD 2-Clause License
|
||||||
|
|
||||||
|
Copyright (c) 2016, Sean Chittenden
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -0,0 +1,7 @@
|
||||||
|
package pgacl
|
||||||
|
|
||||||
|
// ACL is a generic interface that all pgacl types must adhere to
|
||||||
|
type ACL interface {
|
||||||
|
// String creates a PostgreSQL compatible ACL string
|
||||||
|
String() string
|
||||||
|
}
|
|
@ -0,0 +1,89 @@
|
||||||
|
package pgacl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Schema models the privileges of a schema
|
||||||
|
type Schema struct {
|
||||||
|
Role string
|
||||||
|
Create bool
|
||||||
|
CreateGrant bool
|
||||||
|
Usage bool
|
||||||
|
UsageGrant bool
|
||||||
|
}
|
||||||
|
|
||||||
|
const numSchemaOpts = 4
|
||||||
|
|
||||||
|
// NewSchema parses a PostgreSQL ACL string for a schema and returns a Schema
|
||||||
|
// object
|
||||||
|
func NewSchema(aclStr string) (Schema, error) {
|
||||||
|
acl := Schema{}
|
||||||
|
idx := strings.IndexByte(aclStr, '=')
|
||||||
|
if idx == -1 {
|
||||||
|
return Schema{}, fmt.Errorf("invalid aclStr format: %+q", aclStr)
|
||||||
|
}
|
||||||
|
|
||||||
|
acl.Role = aclStr[:idx]
|
||||||
|
|
||||||
|
aclLen := len(aclStr)
|
||||||
|
var i int
|
||||||
|
withGrant := func() bool {
|
||||||
|
if i+1 >= aclLen {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if aclStr[i+1] == '*' {
|
||||||
|
i++
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for i = idx + 1; i < aclLen; i++ {
|
||||||
|
switch aclStr[i] {
|
||||||
|
case 'C':
|
||||||
|
acl.Create = true
|
||||||
|
if withGrant() {
|
||||||
|
acl.CreateGrant = true
|
||||||
|
}
|
||||||
|
case 'U':
|
||||||
|
acl.Usage = true
|
||||||
|
if withGrant() {
|
||||||
|
acl.UsageGrant = true
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return Schema{}, fmt.Errorf("invalid byte %c in schema ACL at %d: %+q", aclStr[i], i, aclStr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return acl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// String creates a PostgreSQL native output for the ACLs that apply to a
|
||||||
|
// schema.
|
||||||
|
func (s Schema) String() string {
|
||||||
|
b := new(bytes.Buffer)
|
||||||
|
b.Grow(len(s.Role) + numSchemaOpts + 1)
|
||||||
|
|
||||||
|
fmt.Fprint(b, s.Role, "=")
|
||||||
|
|
||||||
|
if s.Usage {
|
||||||
|
fmt.Fprint(b, "U")
|
||||||
|
if s.UsageGrant {
|
||||||
|
fmt.Fprint(b, "*")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.Create {
|
||||||
|
fmt.Fprint(b, "C")
|
||||||
|
if s.CreateGrant {
|
||||||
|
fmt.Fprint(b, "*")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
|
@ -2268,6 +2268,12 @@
|
||||||
"revision": "88132ecdd39da62f7c73c5a8e1a383d7da5e0e09",
|
"revision": "88132ecdd39da62f7c73c5a8e1a383d7da5e0e09",
|
||||||
"revisionTime": "2016-10-27T15:40:24Z"
|
"revisionTime": "2016-10-27T15:40:24Z"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"checksumSHA1": "fCtp8mJPTtEMkpDz8TgRtJzMYw0=",
|
||||||
|
"path": "github.com/sean-/pgacl",
|
||||||
|
"revision": "3d9f301f1bf3d5b590119b28132d2e8d5d1f1a62",
|
||||||
|
"revisionTime": "2016-12-15T16:51:43Z"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"checksumSHA1": "BqtlwAjgFuHsVVdnw+dGSe+CKLM=",
|
"checksumSHA1": "BqtlwAjgFuHsVVdnw+dGSe+CKLM=",
|
||||||
"path": "github.com/sethvargo/go-fastly",
|
"path": "github.com/sethvargo/go-fastly",
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
---
|
||||||
|
layout: "postgresql"
|
||||||
|
page_title: "PostgreSQL: postgresql_schema_policy"
|
||||||
|
sidebar_current: "docs-postgresql-resource-postgresql_schema_policy"
|
||||||
|
description: |-
|
||||||
|
Manages the permissions of PostgreSQL schemas.
|
||||||
|
---
|
||||||
|
|
||||||
|
# postgresql\_schema\_policy
|
||||||
|
|
||||||
|
The ``postgresql_schema_policy`` resource applies the necessary SQL DCL
|
||||||
|
(`GRANT`s and `REVOKE`s) necessary to ensure access compliance to a particular
|
||||||
|
SCHEMA within a PostgreSQL database.
|
||||||
|
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "postgresql_role" "my_app" {
|
||||||
|
name = "my_app"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "postgresql_schema" "my_schema" {
|
||||||
|
name = "my_schema"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "postgresql_schema_policy" "my_schema" {
|
||||||
|
create = true
|
||||||
|
usage = true
|
||||||
|
schema = "${postgresql_schema.my_schema.name}"
|
||||||
|
role = "${postgresql_role.my_app.name}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Argument Reference
|
||||||
|
|
||||||
|
* `create` - (Optional) Should the specified ROLE have CREATE privileges to the specified SCHEMA.
|
||||||
|
|
||||||
|
* `create_with_grant` - (Optional) Should the specified ROLE have CREATE privileges to the specified SCHEMA and the ability to GRANT the CREATE privilege to other ROLEs.
|
||||||
|
|
||||||
|
* `usage` - (Optional) Should the specified ROLE have USAGE privileges to the specified SCHEMA.
|
||||||
|
|
||||||
|
* `usage_with_grant` - (Optional) Should the specified ROLE have USAGE privileges to the specified SCHEMA and the ability to GRANT the USAGE privilege to other ROLEs.
|
||||||
|
|
||||||
|
* `role` - (Required) The ROLE who is receiving the policy.
|
||||||
|
|
||||||
|
* `schema` - (Required) The SCHEMA that is the target of the policy.
|
||||||
|
|
||||||
|
## Import Example
|
||||||
|
|
||||||
|
`postgresql_schema_policy` supports importing resources. Supposing the
|
||||||
|
following Terraform:
|
||||||
|
|
||||||
|
```
|
||||||
|
resource "postgresql_schema" "public" {
|
||||||
|
name = "public"
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "postgresql_schema_policy" "public" {
|
||||||
|
create = true
|
||||||
|
usage = true
|
||||||
|
schema = "${postgresql_schema.public.name}"
|
||||||
|
role = "${postgresql_role.my_app.name}"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It is possible to import a `postgresql_schema_policy` resource with the
|
||||||
|
following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ terraform import postgresql_schema_policy.public public
|
||||||
|
```
|
||||||
|
|
||||||
|
Where `public` is the name of the schema in the PostgreSQL database and
|
||||||
|
`postgresql_schema_policy.public` is the name of the resource whose state will
|
||||||
|
be populated as a result of the command.
|
|
@ -25,6 +25,9 @@
|
||||||
<li<%= sidebar_current("docs-postgresql-resource-postgresql_schema") %>>
|
<li<%= sidebar_current("docs-postgresql-resource-postgresql_schema") %>>
|
||||||
<a href="/docs/providers/postgresql/r/postgresql_schema.html">postgresql_schema</a>
|
<a href="/docs/providers/postgresql/r/postgresql_schema.html">postgresql_schema</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li<%= sidebar_current("docs-postgresql-resource-postgresql_schema_policy") %>>
|
||||||
|
<a href="/docs/providers/postgresql/r/postgresql_schema_policy.html">postgresql_schema_policy</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
Loading…
Reference in New Issue