Postgres on Google Cloud / Lesson 2 of 8

Why there is no database password

The design you are about to learn looks like extra work until you see what it replaces.

≈ 10 minutes · concepts only · nothing to run

Here is how almost everyone connects to a database they did not put on a cloud: a hostname, a port, a username, a password, and — if someone was being careful — a firewall rule listing the IP addresses allowed to try.

That arrangement has three separate problems, and the managed-Postgres design on Google Cloud exists to solve all three at once.

Problem 1: the password is a bearer token with no expiry

A database password is a string. Whoever holds the string is the user. It does not expire, it is not tied to a person, and it leaves no trace of who used it. Once it is in a .env file it is also in a backup, a container image, a terminal scrollback, a screenshot in a support ticket, and the laptop of everyone who ever ran the app locally.

Rotating it means finding every copy. Teams do not rotate database passwords for exactly this reason.

Problem 2: an IP allowlist grants access to a location

An allowlist says traffic from this address may connect. It says nothing about who sent it. Anything sharing that egress address — another team's job, a compromised container, a NAT gateway shared by an entire cloud region — inherits the permission. And egress addresses move, so the list is either permanently stale or permanently too wide.

Problem 3: the audit log records an address, not a person

When something goes wrong and you ask who ran the statement, an allowlist plus a shared password can tell you an IP and a role name. That is not an answer.

The swap

Google Cloud's managed Postgres offers a different arrangement, and the whole of this course is learning to operate it:

The familiar wayThe way you are learning
Who may reach the instance Anyone at an allowed IP address Anyone holding roles/cloudsql.client — an identity, not a location
What proves identity A long-lived password string A short-lived OAuth 2.0 access token, minted from your Google identity and refreshed automatically
Transport security TLS, if configured, with certificates someone has to manage Mutual TLS with ephemeral client certificates, issued and rotated for you
Audit trail An IP address and a role name A named Google principal on every connection
Offboarding Rotate the password, update every consumer Remove the person from a group

The feature that does this is called IAM database authentication. The database user is your Google identity. The password field is filled with an access token that a piece of client-side software fetches and refreshes on your behalf.

The consequence that surprises people

A leaked connection string is, by itself, useless. There is no secret in it. Host, port, database name and username can go in a README. Without a Google identity holding the right role, none of it connects.

What fills the password field

Access tokens are valid for one hour (Google Cloud docs). A connection pool that lives for days would break constantly if you fetched a token once at startup. So you do not handle tokens at all. Two pieces of Google software do it for you, and choosing between them is the only decision here:

A
The Cloud SQL Auth Proxy — a binary you run
Listens on a local port, tunnels to the instance, mints and refreshes tokens. Works with any Postgres client: psql, DBeaver, DataGrip, an ORM that only knows how to speak to localhost. This is lesson 3.
B
A language connector — a library you import
Same job, no separate process, available for Java, Python, Go and Node.js. The right choice inside an application. This is lesson 5.

Google calls this arrangement automatic IAM database authentication, to distinguish it from the manual kind where you fetch the token yourself and paste it into the password field. Manual exists. It is for one-off scripting, and it stops working after an hour.

Do not build this yourself

The instinct to write your own token-refresh wrapper is strong and wrong. Token refresh, certificate rotation, instance IP discovery and connection retry are all handled, and the failure modes when they are handled badly are intermittent and awful to debug.

The part the swap does not solve

IAM authentication decides whether you may open a connection. It decides nothing about what you may then do with it. Google's documentation puts it flatly: when an IAM user is added to a database, by default the new database user has no privileges to any database (Google Cloud docs).

So you will connect successfully and be unable to read a single row, and that is the system working correctly. Table permissions are ordinary PostgreSQL GRANTs and have nothing to do with Google. Lesson 4 is about telling these two apart, because every confusing error in this course comes from mistaking one for the other.

Check yourself

A developer pastes a full connection string into a public issue. How bad is it?

Under IAM database authentication there is no password in the string. Host, port, database and username are configuration, not credentials. Worth tidying up; not an incident.

This only holds if the instance genuinely has no password users left on it. A connection string containing password= is exactly as bad as it looks.

Why does a long-running service need the proxy or a connector rather than a token it fetched at startup?

One hour. New connections after that fail, which on a pooled service looks like the database intermittently rejecting logins under load — the pool is simply opening a replacement connection with a dead token.

From memory: name the three weaknesses of "password plus IP allowlist" that this design replaces.

1. The password never expires and cannot practically be rotated. 2. The allowlist grants access to a network position rather than to anyone in particular. 3. The audit trail records an address, not a person.