diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..b70e3e2 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @Pararius/devops diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..c00765d --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,25 @@ +--- +name: Continuous Integration + +on: + pull_request: + +jobs: + check-formatting: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Check formatting + run: terraform fmt -check + + validate-module: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Run terraform init + run: terraform init + + - name: Validate module + run: terraform validate diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cc5778c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.terraform/ +.terraform.lock.hcl diff --git a/README.md b/README.md new file mode 100644 index 0000000..5833bbc --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# Terraform module to manage postgres roles + +See [examples/](examples/) for some examples: +* [simple](examples/simple): Bare minimum; can also be used to quickly check impact of changes made to module +* [full](examples/full): Complete example, including provisioning of GCP CloudSQL instance + +NOTE: Using this module requires a two step approach using separate `terraform apply` runs: +* Provision the postgres instance itself +* Configure the `postgres` provider and provision (additional) roles using this module diff --git a/examples/full/README.md b/examples/full/README.md new file mode 100644 index 0000000..efb7197 --- /dev/null +++ b/examples/full/README.md @@ -0,0 +1,484 @@ +Output: + +```terraform +Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + + create + +Terraform will perform the following actions: + + # module.db.google_sql_database.database["db1"] will be created + + resource "google_sql_database" "database" { + + charset = (known after apply) + + collation = (known after apply) + + id = (known after apply) + + instance = "my-db-instance" + + name = "db1" + + project = (known after apply) + + self_link = (known after apply) + } + + # module.db.google_sql_database.database["db2"] will be created + + resource "google_sql_database" "database" { + + charset = (known after apply) + + collation = (known after apply) + + id = (known after apply) + + instance = "my-db-instance" + + name = "db2" + + project = (known after apply) + + self_link = (known after apply) + } + + # module.db.google_sql_database_instance.instance will be created + + resource "google_sql_database_instance" "instance" { + + connection_name = (known after apply) + + database_version = "POSTGRES_13" + + deletion_protection = true + + first_ip_address = (known after apply) + + id = (known after apply) + + ip_address = (known after apply) + + master_instance_name = (known after apply) + + name = "my-db-instance" + + private_ip_address = (known after apply) + + project = (known after apply) + + public_ip_address = (known after apply) + + region = (known after apply) + + self_link = (known after apply) + + server_ca_cert = (known after apply) + + service_account_email_address = (known after apply) + + + replica_configuration { + + ca_certificate = (known after apply) + + client_certificate = (known after apply) + + client_key = (known after apply) + + connect_retry_interval = (known after apply) + + dump_file_path = (known after apply) + + failover_target = (known after apply) + + master_heartbeat_period = (known after apply) + + password = (sensitive value) + + ssl_cipher = (known after apply) + + username = (known after apply) + + verify_server_certificate = (known after apply) + } + + + settings { + + activation_policy = "ALWAYS" + + availability_type = "REGIONAL" + + disk_autoresize = false + + disk_autoresize_limit = 0 + + disk_size = 0 + + disk_type = "PD_SSD" + + pricing_plan = "PER_USE" + + tier = "db-custom-2-8192" + + user_labels = { + + "env" = "production" + } + + version = (known after apply) + + + backup_configuration { + + enabled = true + + location = "eu" + + start_time = (known after apply) + + transaction_log_retention_days = (known after apply) + + + backup_retention_settings { + + retained_backups = (known after apply) + + retention_unit = (known after apply) + } + } + + + ip_configuration { + + ipv4_enabled = true + + require_ssl = true + } + + + location_preference { + + follow_gae_application = (known after apply) + + zone = (known after apply) + } + + + maintenance_window { + + day = 1 + + hour = 4 + } + } + } + + # module.db.google_sql_user.postgres_user will be created + + resource "google_sql_user" "postgres_user" { + + host = (known after apply) + + id = (known after apply) + + instance = "my-db-instance" + + name = "postgres" + + password = (sensitive value) + + project = (known after apply) + } + + # module.db.random_password.admin_user will be created + + resource "random_password" "admin_user" { + + id = (known after apply) + + length = 48 + + lower = true + + min_lower = 0 + + min_numeric = 0 + + min_special = 0 + + min_upper = 0 + + number = true + + result = (sensitive value) + + special = true + + upper = true + } + + # module.roles.postgresql_default_privileges.role_ro["db1.role2"] will be created + + resource "postgresql_default_privileges" "role_ro" { + + database = "db1" + + id = (known after apply) + + object_type = "table" + + owner = "role2" + + privileges = [ + + "SELECT", + ] + + role = "db1_role_ro" + + schema = "public" + + with_grant_option = false + } + + # module.roles.postgresql_default_privileges.role_ro["db2.role2"] will be created + + resource "postgresql_default_privileges" "role_ro" { + + database = "db2" + + id = (known after apply) + + object_type = "table" + + owner = "role2" + + privileges = [ + + "SELECT", + ] + + role = "db2_role_ro" + + schema = "public" + + with_grant_option = false + } + + # module.roles.postgresql_default_privileges.role_ro["db2.role3"] will be created + + resource "postgresql_default_privileges" "role_ro" { + + database = "db2" + + id = (known after apply) + + object_type = "table" + + owner = "role3" + + privileges = [ + + "SELECT", + ] + + role = "db2_role_ro" + + schema = "public" + + with_grant_option = false + } + + # module.roles.postgresql_default_privileges.role_rw["db1.role2"] will be created + + resource "postgresql_default_privileges" "role_rw" { + + database = "db1" + + id = (known after apply) + + object_type = "table" + + owner = "role2" + + privileges = [ + + "DELETE", + + "INSERT", + + "REFERENCES", + + "SELECT", + + "TRIGGER", + + "TRUNCATE", + + "UPDATE", + ] + + role = "db1_role_rw" + + schema = "public" + + with_grant_option = false + } + + # module.roles.postgresql_default_privileges.role_rw["db2.role2"] will be created + + resource "postgresql_default_privileges" "role_rw" { + + database = "db2" + + id = (known after apply) + + object_type = "table" + + owner = "role2" + + privileges = [ + + "DELETE", + + "INSERT", + + "REFERENCES", + + "SELECT", + + "TRIGGER", + + "TRUNCATE", + + "UPDATE", + ] + + role = "db2_role_rw" + + schema = "public" + + with_grant_option = false + } + + # module.roles.postgresql_default_privileges.role_rw["db2.role3"] will be created + + resource "postgresql_default_privileges" "role_rw" { + + database = "db2" + + id = (known after apply) + + object_type = "table" + + owner = "role3" + + privileges = [ + + "DELETE", + + "INSERT", + + "REFERENCES", + + "SELECT", + + "TRIGGER", + + "TRUNCATE", + + "UPDATE", + ] + + role = "db2_role_rw" + + schema = "public" + + with_grant_option = false + } + + # module.roles.postgresql_grant.role_ro["db1"] will be created + + resource "postgresql_grant" "role_ro" { + + database = "db1" + + id = (known after apply) + + object_type = "table" + + privileges = [ + + "SELECT", + ] + + role = "db1_role_ro" + + schema = "public" + + with_grant_option = false + } + + # module.roles.postgresql_grant.role_ro["db2"] will be created + + resource "postgresql_grant" "role_ro" { + + database = "db2" + + id = (known after apply) + + object_type = "table" + + privileges = [ + + "SELECT", + ] + + role = "db2_role_ro" + + schema = "public" + + with_grant_option = false + } + + # module.roles.postgresql_grant.role_rw["db1"] will be created + + resource "postgresql_grant" "role_rw" { + + database = "db1" + + id = (known after apply) + + object_type = "table" + + privileges = [ + + "DELETE", + + "INSERT", + + "REFERENCES", + + "SELECT", + + "TRIGGER", + + "TRUNCATE", + + "UPDATE", + ] + + role = "db1_role_rw" + + schema = "public" + + with_grant_option = false + } + + # module.roles.postgresql_grant.role_rw["db2"] will be created + + resource "postgresql_grant" "role_rw" { + + database = "db2" + + id = (known after apply) + + object_type = "table" + + privileges = [ + + "DELETE", + + "INSERT", + + "REFERENCES", + + "SELECT", + + "TRIGGER", + + "TRUNCATE", + + "UPDATE", + ] + + role = "db2_role_rw" + + schema = "public" + + with_grant_option = false + } + + # module.roles.postgresql_role.role["role1"] will be created + + resource "postgresql_role" "role" { + + bypass_row_level_security = false + + connection_limit = -1 + + create_database = false + + create_role = false + + encrypted_password = true + + id = (known after apply) + + inherit = true + + login = true + + name = "role1" + + password = (sensitive value) + + replication = false + + roles = [ + + "db1_role_ro", + + "db2_role_ro", + ] + + skip_drop_role = false + + skip_reassign_owned = false + + superuser = false + + valid_until = "infinity" + } + + # module.roles.postgresql_role.role["role2"] will be created + + resource "postgresql_role" "role" { + + bypass_row_level_security = false + + connection_limit = -1 + + create_database = false + + create_role = false + + encrypted_password = true + + id = (known after apply) + + inherit = true + + login = true + + name = "role2" + + password = (sensitive value) + + replication = false + + roles = [ + + "db1_role_rw", + + "db2_role_rw", + ] + + skip_drop_role = false + + skip_reassign_owned = false + + superuser = false + + valid_until = "infinity" + } + + # module.roles.postgresql_role.role["role3"] will be created + + resource "postgresql_role" "role" { + + bypass_row_level_security = false + + connection_limit = -1 + + create_database = false + + create_role = false + + encrypted_password = true + + id = (known after apply) + + inherit = true + + login = true + + name = "role3" + + password = (sensitive value) + + replication = false + + roles = [ + + "db1_role_ro", + + "db2_role_rw", + ] + + skip_drop_role = false + + skip_reassign_owned = false + + superuser = false + + valid_until = "infinity" + } + + # module.roles.postgresql_role.role_ro["db1"] will be created + + resource "postgresql_role" "role_ro" { + + bypass_row_level_security = false + + connection_limit = -1 + + create_database = false + + create_role = false + + encrypted_password = true + + id = (known after apply) + + inherit = true + + login = false + + name = "db1_role_ro" + + replication = false + + skip_drop_role = false + + skip_reassign_owned = false + + superuser = false + + valid_until = "infinity" + } + + # module.roles.postgresql_role.role_ro["db2"] will be created + + resource "postgresql_role" "role_ro" { + + bypass_row_level_security = false + + connection_limit = -1 + + create_database = false + + create_role = false + + encrypted_password = true + + id = (known after apply) + + inherit = true + + login = false + + name = "db2_role_ro" + + replication = false + + skip_drop_role = false + + skip_reassign_owned = false + + superuser = false + + valid_until = "infinity" + } + + # module.roles.postgresql_role.role_rw["db1"] will be created + + resource "postgresql_role" "role_rw" { + + bypass_row_level_security = false + + connection_limit = -1 + + create_database = false + + create_role = false + + encrypted_password = true + + id = (known after apply) + + inherit = true + + login = false + + name = "db1_role_rw" + + replication = false + + skip_drop_role = false + + skip_reassign_owned = false + + superuser = false + + valid_until = "infinity" + } + + # module.roles.postgresql_role.role_rw["db2"] will be created + + resource "postgresql_role" "role_rw" { + + bypass_row_level_security = false + + connection_limit = -1 + + create_database = false + + create_role = false + + encrypted_password = true + + id = (known after apply) + + inherit = true + + login = false + + name = "db2_role_rw" + + replication = false + + skip_drop_role = false + + skip_reassign_owned = false + + superuser = false + + valid_until = "infinity" + } + + # module.roles.random_password.role["role1"] will be created + + resource "random_password" "role" { + + id = (known after apply) + + length = 48 + + lower = true + + min_lower = 0 + + min_numeric = 0 + + min_special = 0 + + min_upper = 0 + + number = true + + result = (sensitive value) + + special = false + + upper = true + } + + # module.roles.random_password.role["role2"] will be created + + resource "random_password" "role" { + + id = (known after apply) + + length = 48 + + lower = true + + min_lower = 0 + + min_numeric = 0 + + min_special = 0 + + min_upper = 0 + + number = true + + result = (sensitive value) + + special = false + + upper = true + } + + # module.roles.random_password.role["role3"] will be created + + resource "random_password" "role" { + + id = (known after apply) + + length = 48 + + lower = true + + min_lower = 0 + + min_numeric = 0 + + min_special = 0 + + min_upper = 0 + + number = true + + result = (sensitive value) + + special = false + + upper = true + } + +Plan: 25 to add, 0 to change, 0 to destroy. +``` \ No newline at end of file diff --git a/examples/full/main.tf b/examples/full/main.tf new file mode 100644 index 0000000..e96aa87 --- /dev/null +++ b/examples/full/main.tf @@ -0,0 +1,56 @@ +terraform { + required_version = "~> 1.0" + required_providers { + google = { + source = "hashicorp/google" + version = ">= 3.70.0" + } + postgresql = { + source = "cyrilgdn/postgresql" + version = "1.14.0" + } + } +} +provider "google" { + region = "europe-west4" + zone = "europe-west4-a" + project = "my-gcp-project" +} + +module "db" { + source = "github.com/Pararius/terraform-module-gcp-cloudsql-postgres.git" + + database_version = "POSTGRES_13" + databases = ["db1", "db2"] + environment = "production" + highly_available = true + instance_name = "my-db-instance" + storage_autoresize = false +} + +provider "postgresql" { + scheme = "gcppostgres" + host = module.db.connection_name + username = "postgres" + password = module.db.admin_user_password +} + +module "roles" { + # source = "github.com/Pararius/terraform-module-gcp-cloudsql-postgres-roles.git" + source = "../../" + + roles = { + "role1": { + databases_ro = ["db1", "db2"] + databases_rw = [] + }, + "role2": { + databases_ro = [] + databases_rw = ["db1", "db2"] + }, + "role3": { + databases_ro = ["db1"] + databases_rw = ["db2"] + }, + } +} diff --git a/examples/simple/README.md b/examples/simple/README.md new file mode 100644 index 0000000..e5d6c58 --- /dev/null +++ b/examples/simple/README.md @@ -0,0 +1,361 @@ +Output: +```terraform +Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + + create + +Terraform will perform the following actions: + + # module.roles.postgresql_default_privileges.role_ro["db1.role2"] will be created + + resource "postgresql_default_privileges" "role_ro" { + + database = "db1" + + id = (known after apply) + + object_type = "table" + + owner = "role2" + + privileges = [ + + "SELECT", + ] + + role = "db1_role_ro" + + schema = "public" + + with_grant_option = false + } + + # module.roles.postgresql_default_privileges.role_ro["db2.role2"] will be created + + resource "postgresql_default_privileges" "role_ro" { + + database = "db2" + + id = (known after apply) + + object_type = "table" + + owner = "role2" + + privileges = [ + + "SELECT", + ] + + role = "db2_role_ro" + + schema = "public" + + with_grant_option = false + } + + # module.roles.postgresql_default_privileges.role_ro["db2.role3"] will be created + + resource "postgresql_default_privileges" "role_ro" { + + database = "db2" + + id = (known after apply) + + object_type = "table" + + owner = "role3" + + privileges = [ + + "SELECT", + ] + + role = "db2_role_ro" + + schema = "public" + + with_grant_option = false + } + + # module.roles.postgresql_default_privileges.role_rw["db1.role2"] will be created + + resource "postgresql_default_privileges" "role_rw" { + + database = "db1" + + id = (known after apply) + + object_type = "table" + + owner = "role2" + + privileges = [ + + "DELETE", + + "INSERT", + + "REFERENCES", + + "SELECT", + + "TRIGGER", + + "TRUNCATE", + + "UPDATE", + ] + + role = "db1_role_rw" + + schema = "public" + + with_grant_option = false + } + + # module.roles.postgresql_default_privileges.role_rw["db2.role2"] will be created + + resource "postgresql_default_privileges" "role_rw" { + + database = "db2" + + id = (known after apply) + + object_type = "table" + + owner = "role2" + + privileges = [ + + "DELETE", + + "INSERT", + + "REFERENCES", + + "SELECT", + + "TRIGGER", + + "TRUNCATE", + + "UPDATE", + ] + + role = "db2_role_rw" + + schema = "public" + + with_grant_option = false + } + + # module.roles.postgresql_default_privileges.role_rw["db2.role3"] will be created + + resource "postgresql_default_privileges" "role_rw" { + + database = "db2" + + id = (known after apply) + + object_type = "table" + + owner = "role3" + + privileges = [ + + "DELETE", + + "INSERT", + + "REFERENCES", + + "SELECT", + + "TRIGGER", + + "TRUNCATE", + + "UPDATE", + ] + + role = "db2_role_rw" + + schema = "public" + + with_grant_option = false + } + + # module.roles.postgresql_grant.role_ro["db1"] will be created + + resource "postgresql_grant" "role_ro" { + + database = "db1" + + id = (known after apply) + + object_type = "table" + + privileges = [ + + "SELECT", + ] + + role = "db1_role_ro" + + schema = "public" + + with_grant_option = false + } + + # module.roles.postgresql_grant.role_ro["db2"] will be created + + resource "postgresql_grant" "role_ro" { + + database = "db2" + + id = (known after apply) + + object_type = "table" + + privileges = [ + + "SELECT", + ] + + role = "db2_role_ro" + + schema = "public" + + with_grant_option = false + } + + # module.roles.postgresql_grant.role_rw["db1"] will be created + + resource "postgresql_grant" "role_rw" { + + database = "db1" + + id = (known after apply) + + object_type = "table" + + privileges = [ + + "DELETE", + + "INSERT", + + "REFERENCES", + + "SELECT", + + "TRIGGER", + + "TRUNCATE", + + "UPDATE", + ] + + role = "db1_role_rw" + + schema = "public" + + with_grant_option = false + } + + # module.roles.postgresql_grant.role_rw["db2"] will be created + + resource "postgresql_grant" "role_rw" { + + database = "db2" + + id = (known after apply) + + object_type = "table" + + privileges = [ + + "DELETE", + + "INSERT", + + "REFERENCES", + + "SELECT", + + "TRIGGER", + + "TRUNCATE", + + "UPDATE", + ] + + role = "db2_role_rw" + + schema = "public" + + with_grant_option = false + } + + # module.roles.postgresql_role.role["role1"] will be created + + resource "postgresql_role" "role" { + + bypass_row_level_security = false + + connection_limit = -1 + + create_database = false + + create_role = false + + encrypted_password = true + + id = (known after apply) + + inherit = true + + login = true + + name = "role1" + + password = (sensitive value) + + replication = false + + roles = [ + + "db1_role_ro", + + "db2_role_ro", + ] + + skip_drop_role = false + + skip_reassign_owned = false + + superuser = false + + valid_until = "infinity" + } + + # module.roles.postgresql_role.role["role2"] will be created + + resource "postgresql_role" "role" { + + bypass_row_level_security = false + + connection_limit = -1 + + create_database = false + + create_role = false + + encrypted_password = true + + id = (known after apply) + + inherit = true + + login = true + + name = "role2" + + password = (sensitive value) + + replication = false + + roles = [ + + "db1_role_rw", + + "db2_role_rw", + ] + + skip_drop_role = false + + skip_reassign_owned = false + + superuser = false + + valid_until = "infinity" + } + + # module.roles.postgresql_role.role["role3"] will be created + + resource "postgresql_role" "role" { + + bypass_row_level_security = false + + connection_limit = -1 + + create_database = false + + create_role = false + + encrypted_password = true + + id = (known after apply) + + inherit = true + + login = true + + name = "role3" + + password = (sensitive value) + + replication = false + + roles = [ + + "db1_role_ro", + + "db2_role_rw", + ] + + skip_drop_role = false + + skip_reassign_owned = false + + superuser = false + + valid_until = "infinity" + } + + # module.roles.postgresql_role.role_ro["db1"] will be created + + resource "postgresql_role" "role_ro" { + + bypass_row_level_security = false + + connection_limit = -1 + + create_database = false + + create_role = false + + encrypted_password = true + + id = (known after apply) + + inherit = true + + login = false + + name = "db1_role_ro" + + replication = false + + skip_drop_role = false + + skip_reassign_owned = false + + superuser = false + + valid_until = "infinity" + } + + # module.roles.postgresql_role.role_ro["db2"] will be created + + resource "postgresql_role" "role_ro" { + + bypass_row_level_security = false + + connection_limit = -1 + + create_database = false + + create_role = false + + encrypted_password = true + + id = (known after apply) + + inherit = true + + login = false + + name = "db2_role_ro" + + replication = false + + skip_drop_role = false + + skip_reassign_owned = false + + superuser = false + + valid_until = "infinity" + } + + # module.roles.postgresql_role.role_rw["db1"] will be created + + resource "postgresql_role" "role_rw" { + + bypass_row_level_security = false + + connection_limit = -1 + + create_database = false + + create_role = false + + encrypted_password = true + + id = (known after apply) + + inherit = true + + login = false + + name = "db1_role_rw" + + replication = false + + skip_drop_role = false + + skip_reassign_owned = false + + superuser = false + + valid_until = "infinity" + } + + # module.roles.postgresql_role.role_rw["db2"] will be created + + resource "postgresql_role" "role_rw" { + + bypass_row_level_security = false + + connection_limit = -1 + + create_database = false + + create_role = false + + encrypted_password = true + + id = (known after apply) + + inherit = true + + login = false + + name = "db2_role_rw" + + replication = false + + skip_drop_role = false + + skip_reassign_owned = false + + superuser = false + + valid_until = "infinity" + } + + # module.roles.random_password.role["role1"] will be created + + resource "random_password" "role" { + + id = (known after apply) + + length = 48 + + lower = true + + min_lower = 0 + + min_numeric = 0 + + min_special = 0 + + min_upper = 0 + + number = true + + result = (sensitive value) + + special = false + + upper = true + } + + # module.roles.random_password.role["role2"] will be created + + resource "random_password" "role" { + + id = (known after apply) + + length = 48 + + lower = true + + min_lower = 0 + + min_numeric = 0 + + min_special = 0 + + min_upper = 0 + + number = true + + result = (sensitive value) + + special = false + + upper = true + } + + # module.roles.random_password.role["role3"] will be created + + resource "random_password" "role" { + + id = (known after apply) + + length = 48 + + lower = true + + min_lower = 0 + + min_numeric = 0 + + min_special = 0 + + min_upper = 0 + + number = true + + result = (sensitive value) + + special = false + + upper = true + } + +Plan: 20 to add, 0 to change, 0 to destroy. +``` \ No newline at end of file diff --git a/examples/simple/main.tf b/examples/simple/main.tf new file mode 100644 index 0000000..9446708 --- /dev/null +++ b/examples/simple/main.tf @@ -0,0 +1,19 @@ +module "roles" { + # source = "git@github.com:Pararius/terraform-module-gcp-cloudsql-postgres-roles.git" + source = "../../" + + roles = { + "role1": { + databases_ro = ["db1", "db2"] + databases_rw = [] + }, + "role2": { + databases_ro = [] + databases_rw = ["db1", "db2"] + }, + "role3": { + databases_ro = ["db1"] + databases_rw = ["db2"] + }, + } +} diff --git a/locals.tf b/locals.tf new file mode 100644 index 0000000..94b7602 --- /dev/null +++ b/locals.tf @@ -0,0 +1,35 @@ +locals { + databases = toset(distinct(flatten([ + for role in var.roles : [role.databases_ro, role.databases_rw] + ]))) + + databases_readers = flatten([ + for role, role_ in var.roles : [ + for database in role_.databases_ro : { + role = role + database = database + } + ] + ]) + databases_writers = flatten([ + for role, role_ in var.roles : [ + for database in role_.databases_rw : { + role = role + database = database + } + ] + ]) + + privileges_ro = [ + "SELECT", + ] + privileges_rw = [ + "DELETE", + "INSERT", + "REFERENCES", + "SELECT", + "TRIGGER", + "TRUNCATE", + "UPDATE", + ] +} diff --git a/main.tf b/main.tf new file mode 100644 index 0000000..bc5a01e --- /dev/null +++ b/main.tf @@ -0,0 +1,9 @@ +terraform { + required_version = ">= 1.0.0" + required_providers { + postgresql = { + source = "cyrilgdn/postgresql" + version = "1.14.0" + } + } +} diff --git a/roles.tf b/roles.tf new file mode 100644 index 0000000..f9d3c4f --- /dev/null +++ b/roles.tf @@ -0,0 +1,92 @@ +resource "random_password" "role" { + for_each = var.roles + + length = 48 + special = false +} + +resource "postgresql_role" "role" { + for_each = var.roles + + name = each.key + superuser = false + create_database = false + create_role = false + inherit = true + login = true + replication = false + bypass_row_level_security = false + connection_limit = -1 + encrypted_password = true + password = random_password.role[each.key].result + roles = concat( + [for database in each.value.databases_ro : "${database}_role_ro"], + [for database in each.value.databases_rw : "${database}_role_rw"], + ) + search_path = ["$user", "public"] + valid_until = "infinity" + skip_drop_role = false + skip_reassign_owned = false + statement_timeout = 0 +} + +resource "postgresql_role" "role_ro" { + for_each = local.databases + + name = "${each.value}_role_ro" + login = false +} + +resource "postgresql_default_privileges" "role_ro" { + for_each = { + for database_writer in local.databases_writers : "${database_writer.database}.${database_writer.role}" => database_writer + } + + database = each.value.database + role = postgresql_role.role_ro[each.value.database].name + owner = each.value.role + schema = "public" + object_type = "table" + privileges = local.privileges_ro +} + +resource "postgresql_grant" "role_ro" { + for_each = local.databases + + database = each.value + role = postgresql_role.role_ro[each.value].name + schema = "public" + object_type = "table" + privileges = local.privileges_ro +} + +resource "postgresql_role" "role_rw" { + for_each = local.databases + + name = "${each.value}_role_rw" + login = false +} + +resource "postgresql_default_privileges" "role_rw" { + for_each = { + for database_writer in local.databases_writers : "${database_writer.database}.${database_writer.role}" => database_writer + } + + database = each.value.database + role = postgresql_role.role_rw[each.value.database].name + owner = each.value.role + schema = "public" + object_type = "table" + privileges = local.privileges_rw +} + +resource "postgresql_grant" "role_rw" { + for_each = local.databases + + database = each.value + role = postgresql_role.role_rw[each.value].name + schema = "public" + object_type = "table" + privileges = local.privileges_rw +} + diff --git a/variables.tf b/variables.tf new file mode 100644 index 0000000..fa8c8b8 --- /dev/null +++ b/variables.tf @@ -0,0 +1,6 @@ +variable "roles" { + type = map(object({ + databases_ro = list(string) + databases_rw = list(string) + })) +}