From 4f185ef934308f3be6518714dfdac64e2b2ae4ab Mon Sep 17 00:00:00 2001 From: Aaron Tatar Date: Mon, 31 Mar 2025 10:17:18 +1100 Subject: [PATCH 1/4] Add support for setting the "pg_audit.log" configuration setting for roles. This follows the patten of how other configuration options are set, e.g. `statement_timeout`. Implements https://github.com/cyrilgdn/terraform-provider-postgresql/issues/529 --- postgresql/resource_postgresql_role.go | 53 ++++++++++++++++++++ postgresql/resource_postgresql_role_test.go | 4 ++ website/docs/r/postgresql_role.html.markdown | 2 + 3 files changed, 59 insertions(+) diff --git a/postgresql/resource_postgresql_role.go b/postgresql/resource_postgresql_role.go index 5b346f3b..7c516eee 100644 --- a/postgresql/resource_postgresql_role.go +++ b/postgresql/resource_postgresql_role.go @@ -32,6 +32,7 @@ const ( roleSuperuserAttr = "superuser" roleValidUntilAttr = "valid_until" roleRolesAttr = "roles" + rolePgAuditLogAttr = "pg_audit_log" roleSearchPathAttr = "search_path" roleStatementTimeoutAttr = "statement_timeout" roleAssumeRoleAttr = "assume_role" @@ -173,6 +174,11 @@ func resourcePostgreSQLRole() *schema.Resource { Optional: true, Description: "Role to switch to at login", }, + rolePgAuditLogAttr: { + Type: schema.TypeString, + Optional: true, + Description: "Controls the behavior of the pg_audit logging", + }, }, } } @@ -311,6 +317,10 @@ func resourcePostgreSQLRoleCreate(db *DBConnection, d *schema.ResourceData) erro return err } + if err = setPgAuditLog(txn, d); err != nil { + return err + } + if err = txn.Commit(); err != nil { return fmt.Errorf("could not commit transaction: %w", err) } @@ -457,6 +467,7 @@ func resourcePostgreSQLRoleReadImpl(db *DBConnection, d *schema.ResourceData) er d.Set(roleRolesAttr, pgArrayToSet(roleRoles)) d.Set(roleSearchPathAttr, readSearchPath(roleConfig)) d.Set(roleAssumeRoleAttr, readAssumeRole(roleConfig)) + d.Set(rolePgAuditLogAttr, readPgAuditLog(roleConfig)) statementTimeout, err := readStatementTimeout(roleConfig) if err != nil { @@ -547,6 +558,19 @@ func readAssumeRole(roleConfig pq.ByteaArray) string { return res } +// readPgAuditLog searches for a pg_audit.log entry in the rolconfig array. +// In case no such value is present, it returns empty string. +func readPgAuditLog(roleConfig pq.ByteaArray) string { + var pgAuditLogAttr = "pg_audit.log" + for _, v := range roleConfig { + config := string(v) + if strings.HasPrefix(config, pgAuditLogAttr) { + return strings.TrimPrefix(config, pgAuditLogAttr+"=") + } + } + return "" +} + // readRolePassword reads password either from Postgres if admin user is a superuser // or only from Terraform state. func readRolePassword(db *DBConnection, d *schema.ResourceData, roleCanLogin bool) (string, error) { @@ -689,6 +713,10 @@ func resourcePostgreSQLRoleUpdate(db *DBConnection, d *schema.ResourceData) erro return err } + if err = setPgAuditLog(txn, d); err != nil { + return err + } + if err = txn.Commit(); err != nil { return fmt.Errorf("could not commit transaction: %w", err) } @@ -1062,3 +1090,28 @@ func setAssumeRole(txn *sql.Tx, d *schema.ResourceData) error { } return nil } + +func setPgAuditLog(txn *sql.Tx, d *schema.ResourceData) error { + if !d.HasChange(rolePgAuditLogAttr) { + return nil + } + + roleName := d.Get(roleNameAttr).(string) + pgAuditLog := d.Get(rolePgAuditLogAttr).(string) + if pgAuditLog != "" { + sql := fmt.Sprintf( + "ALTER ROLE %s SET pg_audit.log TO '%s'", pq.QuoteIdentifier(roleName), pqQuoteLiteral(pgAuditLog), + ) + if _, err := txn.Exec(sql); err != nil { + return fmt.Errorf("could not set pg_audit.log %s for %s: %w", pgAuditLog, roleName, err) + } + } else { + sql := fmt.Sprintf( + "ALTER ROLE %s RESET pg_audit.log", pq.QuoteIdentifier(roleName), + ) + if _, err := txn.Exec(sql); err != nil { + return fmt.Errorf("could not reset pg_audit.log for %s: %w", roleName, err) + } + } + return nil +} diff --git a/postgresql/resource_postgresql_role_test.go b/postgresql/resource_postgresql_role_test.go index fc958840..3e46cc22 100644 --- a/postgresql/resource_postgresql_role_test.go +++ b/postgresql/resource_postgresql_role_test.go @@ -122,6 +122,7 @@ resource "postgresql_role" "update_role" { statement_timeout = 30000 idle_in_transaction_session_timeout = 60000 assume_role = "${postgresql_role.group_role.name}" + pg_audit_log = "all, -read" } ` resource.Test(t, resource.TestCase{ @@ -146,6 +147,7 @@ resource "postgresql_role" "update_role" { resource.TestCheckResourceAttr("postgresql_role.update_role", "statement_timeout", "0"), resource.TestCheckResourceAttr("postgresql_role.update_role", "idle_in_transaction_session_timeout", "0"), resource.TestCheckResourceAttr("postgresql_role.update_role", "assume_role", ""), + resource.TestCheckResourceAttr("postgresql_role.update_role", "pg_audit_log", ""), testAccCheckRoleCanLogin(t, "update_role", "toto"), ), }, @@ -167,6 +169,7 @@ resource "postgresql_role" "update_role" { resource.TestCheckResourceAttr("postgresql_role.update_role", "statement_timeout", "30000"), resource.TestCheckResourceAttr("postgresql_role.update_role", "idle_in_transaction_session_timeout", "60000"), resource.TestCheckResourceAttr("postgresql_role.update_role", "assume_role", "group_role"), + resource.TestCheckResourceAttr("postgresql_role.update_role", "pg_audit_log", "all, -read"), testAccCheckRoleCanLogin(t, "update_role2", "titi"), ), }, @@ -185,6 +188,7 @@ resource "postgresql_role" "update_role" { resource.TestCheckResourceAttr("postgresql_role.update_role", "statement_timeout", "0"), resource.TestCheckResourceAttr("postgresql_role.update_role", "idle_in_transaction_session_timeout", "0"), resource.TestCheckResourceAttr("postgresql_role.update_role", "assume_role", ""), + resource.TestCheckResourceAttr("postgresql_role.update_role", "pg_audit_log", ""), testAccCheckRoleCanLogin(t, "update_role", "toto"), ), }, diff --git a/website/docs/r/postgresql_role.html.markdown b/website/docs/r/postgresql_role.html.markdown index 2c9431be..7adc9f04 100644 --- a/website/docs/r/postgresql_role.html.markdown +++ b/website/docs/r/postgresql_role.html.markdown @@ -118,6 +118,8 @@ resource "postgresql_role" "my_replication_role" { * `assume_role` - (Optional) Defines the role to switch to at login via [`SET ROLE`](https://www.postgresql.org/docs/current/sql-set-role.html). +* `pg_audit_log` - (Optional) Controls the behavior of the [`pg_audit`](https://github.com/pgaudit/pgaudit) logging by setting the [`pg_audit.log`](https://github.com/pgaudit/pgaudit/blob/master/README.md#pgauditlog) parameter. + ## Import Example `postgresql_role` supports importing resources. Supposing the following From 4b9014a3fecb4127961d3d6ddb2c3f3aece06036 Mon Sep 17 00:00:00 2001 From: Aaron Tatar Date: Fri, 4 Apr 2025 16:11:49 +0200 Subject: [PATCH 2/4] Fix the configuration setting used for pgaudit_log and improve the tests --- postgresql/resource_postgresql_role.go | 6 +++--- postgresql/resource_postgresql_role_test.go | 16 ++++++++++++---- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/postgresql/resource_postgresql_role.go b/postgresql/resource_postgresql_role.go index 7c516eee..34b8bb81 100644 --- a/postgresql/resource_postgresql_role.go +++ b/postgresql/resource_postgresql_role.go @@ -32,7 +32,7 @@ const ( roleSuperuserAttr = "superuser" roleValidUntilAttr = "valid_until" roleRolesAttr = "roles" - rolePgAuditLogAttr = "pg_audit_log" + rolePgAuditLogAttr = "pgaudit_log" roleSearchPathAttr = "search_path" roleStatementTimeoutAttr = "statement_timeout" roleAssumeRoleAttr = "assume_role" @@ -177,7 +177,7 @@ func resourcePostgreSQLRole() *schema.Resource { rolePgAuditLogAttr: { Type: schema.TypeString, Optional: true, - Description: "Controls the behavior of the pg_audit logging", + Description: "Controls the behavior of the pg_audit extension by setting pgaudit.log. See https://github.com/pgaudit/pgaudit/blob/main/README.md#pgauditlog", }, }, } @@ -561,7 +561,7 @@ func readAssumeRole(roleConfig pq.ByteaArray) string { // readPgAuditLog searches for a pg_audit.log entry in the rolconfig array. // In case no such value is present, it returns empty string. func readPgAuditLog(roleConfig pq.ByteaArray) string { - var pgAuditLogAttr = "pg_audit.log" + var pgAuditLogAttr = "pgaudit.log" for _, v := range roleConfig { config := string(v) if strings.HasPrefix(config, pgAuditLogAttr) { diff --git a/postgresql/resource_postgresql_role_test.go b/postgresql/resource_postgresql_role_test.go index 3e46cc22..b9fa96e6 100644 --- a/postgresql/resource_postgresql_role_test.go +++ b/postgresql/resource_postgresql_role_test.go @@ -58,6 +58,9 @@ func TestAccPostgresqlRole_Basic(t *testing.T) { resource.TestCheckResourceAttr("postgresql_role.sub_role", "roles.1", "role_simple"), testAccCheckPostgresqlRoleExists("role_with_search_path", nil, []string{"bar", "foo-with-hyphen"}), + + testAccCheckPostgresqlRoleExists("role_with_pgaudit_log", nil, nil), + resource.TestCheckResourceAttr("postgresql_role.role_with_pgaudit_log", "pgaudit_log", "all"), ), }, }, @@ -122,7 +125,7 @@ resource "postgresql_role" "update_role" { statement_timeout = 30000 idle_in_transaction_session_timeout = 60000 assume_role = "${postgresql_role.group_role.name}" - pg_audit_log = "all, -read" + pgaudit_log = "all, -read" } ` resource.Test(t, resource.TestCase{ @@ -147,7 +150,7 @@ resource "postgresql_role" "update_role" { resource.TestCheckResourceAttr("postgresql_role.update_role", "statement_timeout", "0"), resource.TestCheckResourceAttr("postgresql_role.update_role", "idle_in_transaction_session_timeout", "0"), resource.TestCheckResourceAttr("postgresql_role.update_role", "assume_role", ""), - resource.TestCheckResourceAttr("postgresql_role.update_role", "pg_audit_log", ""), + resource.TestCheckResourceAttr("postgresql_role.update_role", "pgaudit_log", ""), testAccCheckRoleCanLogin(t, "update_role", "toto"), ), }, @@ -169,7 +172,7 @@ resource "postgresql_role" "update_role" { resource.TestCheckResourceAttr("postgresql_role.update_role", "statement_timeout", "30000"), resource.TestCheckResourceAttr("postgresql_role.update_role", "idle_in_transaction_session_timeout", "60000"), resource.TestCheckResourceAttr("postgresql_role.update_role", "assume_role", "group_role"), - resource.TestCheckResourceAttr("postgresql_role.update_role", "pg_audit_log", "all, -read"), + resource.TestCheckResourceAttr("postgresql_role.update_role", "pgaudit_log", "all, -read"), testAccCheckRoleCanLogin(t, "update_role2", "titi"), ), }, @@ -188,7 +191,7 @@ resource "postgresql_role" "update_role" { resource.TestCheckResourceAttr("postgresql_role.update_role", "statement_timeout", "0"), resource.TestCheckResourceAttr("postgresql_role.update_role", "idle_in_transaction_session_timeout", "0"), resource.TestCheckResourceAttr("postgresql_role.update_role", "assume_role", ""), - resource.TestCheckResourceAttr("postgresql_role.update_role", "pg_audit_log", ""), + resource.TestCheckResourceAttr("postgresql_role.update_role", "pgaudit_log", ""), testAccCheckRoleCanLogin(t, "update_role", "toto"), ), }, @@ -450,4 +453,9 @@ resource "postgresql_role" "role_with_search_path" { name = "role_with_search_path" search_path = ["bar", "foo-with-hyphen"] } + +resource "postgresql_role" "role_with_pgaudit_log" { + name = "role_with_pgaudit_log" + pgaudit_log = "all" +} ` From 3a3917d3243a49690a44aa70195a351ad730d022 Mon Sep 17 00:00:00 2001 From: Aaron Tatar Date: Fri, 4 Apr 2025 16:29:33 +0200 Subject: [PATCH 3/4] Fix documentation --- website/docs/r/postgresql_role.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/postgresql_role.html.markdown b/website/docs/r/postgresql_role.html.markdown index 7adc9f04..10150b9f 100644 --- a/website/docs/r/postgresql_role.html.markdown +++ b/website/docs/r/postgresql_role.html.markdown @@ -118,7 +118,7 @@ resource "postgresql_role" "my_replication_role" { * `assume_role` - (Optional) Defines the role to switch to at login via [`SET ROLE`](https://www.postgresql.org/docs/current/sql-set-role.html). -* `pg_audit_log` - (Optional) Controls the behavior of the [`pg_audit`](https://github.com/pgaudit/pgaudit) logging by setting the [`pg_audit.log`](https://github.com/pgaudit/pgaudit/blob/master/README.md#pgauditlog) parameter. +* `pgaudit_log` - (Optional) Controls the behavior of the [`pgaudit`](https://github.com/pgaudit/pgaudit) extension by setting the [`pgaudit.log`](https://github.com/pgaudit/pgaudit/blob/master/README.md#pgauditlog) parameter. ## Import Example From 52ca534e3380d748c3a966d126f213cdf465d9aa Mon Sep 17 00:00:00 2001 From: Aaron Tatar Date: Mon, 7 Apr 2025 10:47:55 +0200 Subject: [PATCH 4/4] Fix some more missed instances of pg_audit --- postgresql/resource_postgresql_role.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/postgresql/resource_postgresql_role.go b/postgresql/resource_postgresql_role.go index 34b8bb81..aff6bcdb 100644 --- a/postgresql/resource_postgresql_role.go +++ b/postgresql/resource_postgresql_role.go @@ -177,7 +177,7 @@ func resourcePostgreSQLRole() *schema.Resource { rolePgAuditLogAttr: { Type: schema.TypeString, Optional: true, - Description: "Controls the behavior of the pg_audit extension by setting pgaudit.log. See https://github.com/pgaudit/pgaudit/blob/main/README.md#pgauditlog", + Description: "Controls the behavior of the pgaudit extension by setting pgaudit.log. See https://github.com/pgaudit/pgaudit/blob/main/README.md#pgauditlog", }, }, } @@ -558,7 +558,7 @@ func readAssumeRole(roleConfig pq.ByteaArray) string { return res } -// readPgAuditLog searches for a pg_audit.log entry in the rolconfig array. +// readPgAuditLog searches for a pgaudit.log entry in the rolconfig array. // In case no such value is present, it returns empty string. func readPgAuditLog(roleConfig pq.ByteaArray) string { var pgAuditLogAttr = "pgaudit.log" @@ -1100,17 +1100,17 @@ func setPgAuditLog(txn *sql.Tx, d *schema.ResourceData) error { pgAuditLog := d.Get(rolePgAuditLogAttr).(string) if pgAuditLog != "" { sql := fmt.Sprintf( - "ALTER ROLE %s SET pg_audit.log TO '%s'", pq.QuoteIdentifier(roleName), pqQuoteLiteral(pgAuditLog), + "ALTER ROLE %s SET pgaudit.log TO '%s'", pq.QuoteIdentifier(roleName), pqQuoteLiteral(pgAuditLog), ) if _, err := txn.Exec(sql); err != nil { - return fmt.Errorf("could not set pg_audit.log %s for %s: %w", pgAuditLog, roleName, err) + return fmt.Errorf("could not set pgaudit.log %s for %s: %w", pgAuditLog, roleName, err) } } else { sql := fmt.Sprintf( - "ALTER ROLE %s RESET pg_audit.log", pq.QuoteIdentifier(roleName), + "ALTER ROLE %s RESET pgaudit.log", pq.QuoteIdentifier(roleName), ) if _, err := txn.Exec(sql); err != nil { - return fmt.Errorf("could not reset pg_audit.log for %s: %w", roleName, err) + return fmt.Errorf("could not reset pgaudit.log for %s: %w", roleName, err) } } return nil