diff --git a/README.md b/README.md new file mode 100644 index 0000000..104932f --- /dev/null +++ b/README.md @@ -0,0 +1,88 @@ +# PostgreSQL Application Database Terraform Module + +## Introduction + +PostgreSQL has a robust set of [user roles and permissions](https://www.postgresql.org/docs/11/user-manag.html) that may take some effort to implement, and may often to overlooked by developers focused on building their applications. This module aims to simplify the process of managing database users & permissions so that developers can be assured their data is secured from other users on a [multi-tenant](https://en.wikipedia.org/wiki/Multitenancy) database server instance, letting them focus on their applications instead. + + +## Details + +[Heroku Postgres](https://www.heroku.com/postgres) was a huge inspiration for the author, and this module was designed to emulate the databases created by the _Hobby Dev_ and _Hobby Basic_ plans where: + +1. Databases created will have all permissions revoked so that no users can use or connect to it unless permissions are explicitly granted. +1. Two roles--`_rw_role` and `_ro_role`-- are created with read-write and read-only permsssions respectively. +1. A user with read-write permissions to the database is created, along with a user with read-only permissions, and inherit their permissions from the respective roles. + +This module is probably useful only to those who are running their own PostgreSQL server instance (e.g. AWS RDS, on-premise, etc), while others may prefer the [terraform-heroku-cloud-database](http://github.com/infrastructure-as-code/terraform-heroku-cloud-database) module instead. + + +## Pre-requisites + +1. Terraform 0.12.x. (May work work 0.11.x.) +1. `psql` client on the host that runs the module (needed to work around a missing feature [#46](https://github.com/terraform-providers/terraform-provider-postgresql/issues/46) in the Terraform PostgreSQL provider) +1. A [PostgreSQL server instance](http://www.postgresqltutorial.com/postgresql-server-and-database-objects/) (e.g. AWS RDS, or a vanilla PostgreSQL server), and an user with permissions to create databases and manage users. + + +## Usage + +``` +provider "postgresql" { + host = "ec2-50-16-225-96.compute-1.amazonaws.com" + port = "5432" + database = "postgres" + username = "postgres" + password = "p0stgr3s" + superuser = false +} + +module "tenant_database" { + source = "github.com/infrastructure-as-code/terraform-postgresql-database" + pg_host = "ec2-50-16-225-96.compute-1.amazonaws.com" + pg_port = "5432" + pg_user = "postgres" + pg_password = "p0stgr3s" + database_name = "foo" +} +``` + +The database credentials have to be passed to both the [PostgreSQL provider](https://www.terraform.io/docs/providers/postgresql/index.html) as well as the module itself because there is no way to switch users/databases at the Terraform resource level. There is an issue ([#46](https://github.com/terraform-providers/terraform-provider-postgresql/issues/46)) open with the provider authors for over a year now. + + +### Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|----------| +| database\_name | The name of the new database to create | string | - | yes | +| pg\_host | Hostname of Postgresql server | string | - | yes | +| pg\_port | TCP port that the Postgresql is listening on | string | `5432` | yes | +| pg\_user | Username of Postgresql user who must have createdb permissions | string | - | yes | +| pg\_password | Password of Postgresql user who has createdb permssions | string | - | yes | + + +### Outputs + +| Name | Description | +|------|-------------| +| database\_name | Name of database created | +| rw\_database\_url | Database URL for read-write user | +| ro\_database\_url | Database URL for read-only user | + + +## Use Cases + +1. Creating RDS databases, even when automated, adds minutes to a deployment (e.g. with AWS Elastic Beanstalk), and hurts developer velocity. Instead of creating an RDS instance for each developer/application, one can use this module to create databases on a shared RDS instance and save time. +1. The overheads of running a PostgreSQL instance may add up, and isn't necessarily a good use of resources even with tiny instances (like `db.t2.micro` on RDS), so sharing non-production instances allows low-utilization resources to be used more efficiently. +1. This module creates a read-only user and a read-write user for each database, and locks down the permissions + + +## License + +MIT-Licensed. Please see [LICENSE](LICENSE) for details. + + +## References + +1. [Managing PostgreSQL users and roles](https://aws.amazon.com/blogs/database/managing-postgresql-users-and-roles/) +1. [Heroku Postgres Credentials](https://devcenter.heroku.com/articles/heroku-postgresql-credentials) +1. [Created user can access all databases in PostgreSQL without any grants](https://dba.stackexchange.com/questions/17790/created-user-can-access-all-databases-in-postgresql-without-any-grants) +1. [Locking Down Permissions in PostgreSQL and Redshift](https://blog.dbrhino.com/locking-down-permissions-in-postgresql-and-redshift.html) diff --git a/main.tf b/main.tf index 4aa2a26..a7e3fa6 100644 --- a/main.tf +++ b/main.tf @@ -32,14 +32,6 @@ resource "random_string" "ro_pass" { lower = true } -resource "random_string" "dbsuffix" { - length = 8 - special = false - number = true - upper = false - lower = true -} - locals { db_host = "${var.pg_host}" db_port = "${var.pg_port}" @@ -47,7 +39,7 @@ locals { db_rw_pass = "${random_string.rw_pass.result}" db_ro_user = "uro${random_string.ro_user.result}" db_ro_pass = "${random_string.ro_pass.result}" - db_name = "${var.database_name_prefix}_${random_string.dbsuffix.result}_db" + db_name = "${var.database_name}" } ################################################################################ diff --git a/outputs.tf b/outputs.tf index 0458191..373c420 100644 --- a/outputs.tf +++ b/outputs.tf @@ -2,36 +2,10 @@ output "database_name" { value = "${local.db_name}" } -# rw role -output "db_rw_user" { - value = "${local.db_rw_user}" -} - -output "db_rw_pass" { - value = "${local.db_rw_pass}" -} - -output "rw_psql" { - value = "PGHOST=${local.db_host} PGPORT=${local.db_port} PGUSER=${local.db_rw_user} PGPASSWORD=${local.db_rw_pass} PGDATABASE=${local.db_name} psql" -} - output "rw_database_url" { value = "postgres://${local.db_rw_user}:${local.db_rw_pass}@${local.db_host}:${local.db_port}/${local.db_name}" } -# ro role -output "db_ro_user" { - value = "${local.db_ro_user}" -} - -output "db_ro_pass" { - value = "${local.db_ro_pass}" -} - -output "ro_psql" { - value = "PGHOST=${local.db_host} PGPORT=${local.db_port} PGUSER=${local.db_ro_user} PGPASSWORD=${local.db_ro_pass} PGDATABASE=${local.db_name} psql" -} - output "ro_database_url" { value = "postgres://${local.db_ro_user}:${local.db_ro_pass}@${local.db_host}:${local.db_port}/${local.db_name}" } diff --git a/variables.tf b/variables.tf index 6ccb96b..9ab8b08 100644 --- a/variables.tf +++ b/variables.tf @@ -1,6 +1,6 @@ -variable "database_name_prefix" { +variable "database_name" { type = "string" - description = "Name prefix of database to be created. Actual database name will be returned in output." + description = "Name of database to be created." } variable "pg_host" {