hackforla / hackforla/incubator

Ship a downloadable terraform.tfvars backed by a read-only Postgres role

Open
#209 0 comments 0 reactions 0 assignees View on GitHub
complexity: medium feature: Onboarding role: DevOps Engineer size: 5pt
Dominant language
HCL
Stars
6
Forks
18
Avg merge
1h 9m
Merged PRs (30d)
42

Description

### Overview

We need a contributor to be able to run `terraform plan` locally without being handed the master password for the production database. Today a plan has to reach the database to refresh 18 `postgresql_role` and `postgresql_grant` resources, and the only credential that works is the `postgres` superuser — so a read-only operation currently requires the most privileged account we have.

### Action Items

**1. Make the connecting user configurable — this blocks everything else**

The Postgres provider hardcodes the user, so a read-only role cannot be used at all until this changes.

- [ ] In `terraform/variables.tf`, add a `pguser` variable with `default = "postgres"`. The default is what keeps CI working unchanged — CI passes `TF_VAR_pghost` and `TF_VAR_pgpassword` and knows nothing about a user.
- [ ] In `terraform/backend.tf`, change the `postgresql` provider's `username = "postgres"` (line 21 when written; find it inside the `provider "postgresql"` block) to `username = var.pguser`.

**2. Create the read-only role and find out whether it is actually sufficient**

This is the open technical question and the reason this ticket is not smaller. It may turn out that a genuinely read-only role cannot refresh everything, in which case the role grows and the trade-off has to be re-examined.

- [ ] Create a read-only role on `incubator-prod-database`. Reading roles hits `pg_roles`; reading grants hits catalog ACLs. Start from the least privilege that can see both and add only what the refresh demands.
- [ ] **Acceptance criterion:** run a full `terraform plan` authenticated as that role and confirm it still reports **no changes**. A plan that errors, or that reports spurious changes because it cannot see something, means the role is not sufficient — record what it could not read on this issue before widening it.
- [ ] If the role has to be widened beyond read-only to pass, stop and raise it here rather than proceeding. The whole point of the ticket is that contributors get something weaker than the master credential; a role that ends up close to superuser does not deliver that.

**3. Ship the file**

- [ ] Add `terraform/terraform.tfvars.example` to the repo, with placeholder values and a comment per variable saying what it is and where to get it. This one is committed — it holds no secrets.
- [ ] Store the real `terraform.tfvars` as a **Document** in the DevOps 1Password vault so a contributor can download it directly rather than assembling it by hand.
- [ ] Document the workflow in the repo README: download the file from 1Password, drop it in `terraform/`, run `terraform plan`. Terraform auto-loads `terraform.tfvars` from the working directory, so there is no flag to pass and no shell export — which is the reason for this shape rather than environment variables.
- [ ] State plainly in that documentation that this credential is for `plan` only and that `apply` is not available to contributors.

- [ ] After the PR merges, have someone who has not done the setup before follow the README from scratch and confirm they get a clean plan. The point of the ticket is the first-time experience, and the author is the worst person to judge it.

### Resources/Instructions

- Files to change: [terraform/variables.tf](https://github.com/hackforla/incubator/blob/main/terraform/variables.tf), [terraform/backend.tf](https://github.com/hackforla/incubator/blob/main/terraform/backend.tf), the repo README, plus the new `terraform/terraform.tfvars.example`.
- `terraform.tfvars` is **already gitignored** (`.gitignore:15`), so there is no new ignore rule to add and no accidental-commit path to guard.
- Do not confuse this with the committed `terraform/prod.backend.tfvars`, which is `-backend-config` for `init` and is not secret.
- Only the password is actually secret. `var.pghost` is the RDS endpoint and is readable from `aws rds describe-db-instances`.
- Environment variables (`TF_VAR_pguser` and friends) were considered and rejected for contributors: they need a `source`/`export` step that differs between PowerShell and bash. CI keeps using them; contributors get a file.
- Encouraging sign for the read-only question: the provider already sets `superuser = false`, so it is not taking superuser-only code paths today.
- **Dependency worth knowing:** this works only because the database is reachable from a contributor's machine. That exposure is currently accepted, but if `publicly_accessible` is ever turned off, local `terraform plan` stops working for everyone and this ticket's premise goes with it.
- **Out of scope:** AWS credentials. Contributors get AWS access through their IAM user or Identity Center, not through a downloaded file — that belongs to #139. `devops-security` is also out of scope; it has no Postgres provider and no equivalent need.

Contributor guide

Open the contributing guide

Research direction

Start with terraform/variables.tf and terraform/backend.tf to trace the provider credentials, then inspect how the 18 postgresql_role and postgresql_grant resources are refreshed. Run a full terraform plan with the proposed read-only role and record any catalog access failures. Done means a no-change plan succeeds, terraform/terraform.tfvars.example and the README describe the plan-only workflow, and the real file is stored in the DevOps 1Password vault.

Written by the indexing model from the issue text.

Assessment

Tech stack
postgresql, terraform
Domain
databases, devops, documentation
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.