Skip to content

Commit e2a0f12

Browse files
authored
feat(cli): check service accounts in doctor (#3784)
1 parent 43df057 commit e2a0f12

12 files changed

Lines changed: 782 additions & 13 deletions

File tree

‎AGENTS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Run from the repo root.
3737
| `npm run build` | Production build of every page to `packages/web/dist`. Reads content from the Firestore emulator, see `FIRESTORE_TARGET` below |
3838
| `npm start` | Emulators, functions and the Astro dev server at http://localhost:4321 |
3939
| `npm run serve` | Builds from the emulator data, then serves `dist` on the Hosting emulator at http://localhost:5000 |
40-
| `./hb doctor` | Checks the local setup, Firebase login, project, billing plan and deployed functions |
40+
| `./hb doctor` | Checks the local setup, Firebase login, project, billing plan, deployed functions, deploy roles, service account keys and browser API keys |
4141
| `./hb init` | Sets up a site: Firebase project and web app, event details in `packages/config`, billing, first deploy. Changes production, so only run it when asked |
4242

4343
Before finishing a change, run `npm run lint` and `npm test`, or at least the affected Vitest project and type check.

‎docs/README.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- [Styling](tutorials/03-styling.md)
99
- [Deploy](tutorials/04-deploy.md)
1010
- [Notifications](tutorials/05-notifications.md)
11+
- [Security](tutorials/06-security.md)
1112
- [Firestore utils](tutorials/firebase-utils.md)
1213

1314
## Maintainers

‎docs/tutorials/02-firebase.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ Turn on the sign-in methods in `auth.providers` in the [Firebase console](https:
3535
- `google`: add **Google**.
3636
- `facebook` and `twitter`: add them with the app ID and secret from Facebook or X.
3737

38-
The link in the email opens the page the visitor signed in from. Its domain must be under **Authentication** > **Settings** > **Authorized domains**. Firebase adds `localhost` and the project's `web.app` and `firebaseapp.com` domains. Add a custom domain yourself.
38+
The link in the email opens the page the visitor signed in from. Its domain must be under **Authentication** > **Settings** > **Authorized domains**. Firebase adds `localhost` and the project's `web.app` and `firebaseapp.com` domains. Add a custom domain yourself, and remove `localhost`. See [Security](06-security.md#what-you-set).
3939

4040
Locally, the Auth emulator doesn't send email. Find the sign-in link under **Authentication** in the Emulator UI at http://localhost:4000, or in the emulator's log, and open it in the browser.
4141

‎docs/tutorials/04-deploy.md‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ Your Firebase project must be on the Blaze plan. See [Billing](02-firebase.md#bi
3535
In the [`.github/workflows`](.github/workflows) folder, you can find two workflows to help you develop and deploy Hoverboard to Firebase:
3636

3737
- [`main.yaml`](.github/workflows/main.yaml) Builds the project, runs the linter and the tests on every push.
38+
- [`security.yaml`](.github/workflows/security.yaml) Checks the workflows with zizmor, and the dependencies for known vulnerabilities.
3839
- [`deploy-preview.yaml`](.github/workflows/deploy-preview.yaml) Checks `packages/config` with `./hb validate-config`, then deploys a preview of the website to Firebase after every push to a pull request. Functions and Firestore rules are not deployed. See [Editing on GitHub](01-configure-app.md#editing-on-github).
3940
- [`deploy.yaml`](.github/workflows/deploy.yaml) Deploys the project to Firebase after every push to the `main` branch. You can also run it by hand, for example after you change content in Firestore: open **Actions** > **Deploy** > **Run workflow** on GitHub, or run `gh workflow run deploy.yaml`. It only deploys from `main`. It deploys with `--force`, so it deletes functions that are no longer in the code without asking.
4041

@@ -55,13 +56,15 @@ Both workflows sign in to Google Cloud with Workload Identity Federation. GitHub
5556
The command uses your Firebase CLI login. It needs the Owner role, or permission to create service accounts, workload identity pools and IAM bindings. It is safe to run again. It:
5657

5758
1. Enables the IAM, IAM Credentials, Security Token Service and Cloud Resource Manager APIs.
58-
1. Creates a `github-deploy` service account with the roles a deploy needs: `Firebase Hosting Admin`, `Firebase Rules Admin`, `Cloud Datastore Index Admin`, `Cloud Datastore Viewer` (the build reads your content), `Firebase Storage Viewer`, `Storage Bucket Viewer`, `Cloud Functions Admin`, `Cloud Scheduler Admin`, `Service Usage Consumer` and `Service Account User`. It can't write to Firestore or read Auth users and Storage files. Earlier versions granted `Firebase Admin`, `Cloud Run Admin`, `Artifact Registry Writer` and `Service Usage Admin`, and the command takes them away (verify with a deploy of every target).
59+
1. Creates a `github-deploy` service account with the roles a deploy needs: `Firebase Hosting Admin`, `Firebase Rules Admin`, `Cloud Datastore Index Admin`, `Cloud Datastore Viewer` (the build reads your content), `Firebase Storage Viewer`, `Storage Bucket Viewer`, `Cloud Functions Admin`, `Cloud Scheduler Admin`, `Service Usage Consumer` and `Service Account User`. It can't write to Firestore or read Auth users and Storage files. Earlier versions granted `Firebase Admin`, `Cloud Run Admin`, `Artifact Registry Writer` and `Service Usage Admin`, and the command takes them away.
5960
1. Creates a `github` workload identity pool and provider that only accept tokens from your repository. It reads the repository from the `origin` remote. Pass `--repo owner/name` to choose another one.
6061
1. Sets the `WIF_PROVIDER` and `DEPLOY_SERVICE_ACCOUNT` repository variables with the [GitHub CLI](https://cli.github.com/). Without it, the command prints the values to add in the repository settings.
6162

6263
If the auth step fails with `must specify exactly one of "workload_identity_provider" or "credentials_json"`, the repository variables are missing. Run `./hb setup-github`.
6364

64-
`./hb doctor` checks the Google Cloud and GitHub setup. It warns about anything `./hb setup-github` would still change.
65+
`./hb doctor` checks the Google Cloud and GitHub setup. It warns about anything `./hb setup-github` would still change. It also warns about service account keys, which never expire, and about the `github-action-*` accounts that `firebase init hosting:github` created for deploys before `./hb setup-github`. Delete those keys, the GitHub secrets that held them (such as `FIREBASE_SERVICE_ACCOUNT_*`), and the old accounts.
66+
67+
`./hb doctor` also checks the browser API key of the Firebase web app, and the Maps key in `site.json` when the map is on. Each key should only work from the site's domain and `<project>.firebaseapp.com`, and only call the APIs the site uses with it. The browser key needs the Firebase APIs on [Firebase's list](https://firebase.google.com/docs/projects/api-keys#faq-required-apis-for-restricted-firebase-api-key) for Authentication, Cloud Firestore, Cloud Messaging and Performance Monitoring, and the Maps key needs the Maps JavaScript API. Doctor names any other API a key can call, and links to the key in the Google Cloud console. The check needs the API Keys API, and links to the page that turns it on.
6568

6669
Missing roles show up as `403` errors such as `Permission denied to get service` (Service Usage), a failed `firebaserules.googleapis.com` `:test` request (Rules) or `Failed to list functions` (Cloud Functions).
6770

@@ -72,3 +75,5 @@ The preview and the live deploys use the same service account. A pull request's
7275
The first deploy, from `./hb init` or `./hb deploy`, runs with your own account. It enables the APIs, creates the Firestore database, and grants Google's service agents the roles functions need. The deploy account can't do these, so if a new kind of function needs another API, deploy once from your computer.
7376

7477
You can now push to your `main` branch and it'll deploy to the production (`live`) Firebase Hosting channel and pull requests will deploy a temporary preview.
78+
79+
Before the event, go through the [security checklist](06-security.md#what-you-set).

‎docs/tutorials/06-security.md‎

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Security
2+
3+
What Hoverboard protects for you, and what you set in your own Firebase project and GitHub repository.
4+
5+
## What Hoverboard does
6+
7+
- **Firestore rules.** Visitors can only read your content. You change it in the Firebase console or with `./hb firestore-*`. Signed-in visitors can write only their own bookmarks, notification settings and feedback. The subscribe and partner forms can only add documents, with checked fields and sizes, and nobody can read them from the site. Export them with [`./hb firestore-csv`](01-configure-app.md#subscribers-and-partner-leads).
8+
- **Storage rules.** The site can't read or write your Storage bucket.
9+
- **Content.** Links in your config and content can only be `https:`, `http:`, `mailto:` or a path on your site. The site drops other links, such as `javascript:` ones, even when they come straight from the Firebase console. Markdown is sanitized before it is shown, and the build sanitizes the hero illustration.
10+
- **Headers.** `firebase.json` sends `Strict-Transport-Security`, `Referrer-Policy`, `Permissions-Policy` and other headers on every page, and every page has a [Content Security Policy](01-configure-app.md#content-security-policy).
11+
- **Sign-out.** Signing out deletes the copy of the visitor's data that the site keeps in the browser for offline use.
12+
- **Logs.** The functions don't log emails, push tokens or user IDs.
13+
- **Deploys.** GitHub Actions deploy without a service account key, with only the roles a deploy needs. See [Deploying to Firebase with Github Actions](04-deploy.md#deploying-to-firebase-with-github-actions).
14+
15+
The functions run as the project's default compute service account, which has the Editor role. Only code you deploy runs as it.
16+
17+
## What you set
18+
19+
Do these once when you set up a site, and check them again before the event.
20+
21+
1. **Run `./hb doctor`.** It checks most of the settings below.
22+
1. **API keys.** Restrict the Firebase web app's browser key to your site's domains and to the APIs the site uses. Use a separate key for Google Maps, restricted to your domain and the Maps JavaScript API. `./hb doctor` checks both keys. See [Deploying to Firebase with Github Actions](04-deploy.md#deploying-to-firebase-with-github-actions).
23+
1. **Authorized domains.** In the Firebase console under **Authentication** > **Settings** > **Authorized domains**, keep only your site's domains. Remove `localhost`: local development uses the emulators, not your project. Preview deploys add their own domain. Remove the domains of previews that no longer exist.
24+
1. **Email enumeration protection.** Turn it on under **Authentication** > **Settings** > **User actions**, so sign-in doesn't tell anyone which emails have accounts. New projects have it on (verify).
25+
1. **Budgets.** Set a spend cap and a budget alert, so abuse can't run up a large bill. See [Spend cap and budget](02-firebase.md#spend-cap-and-budget).
26+
1. **Service account keys.** You don't need any. `./hb doctor` warns about keys and about the old `github-action-*` accounts. Delete them, and the GitHub secrets that held them.
27+
1. **Repository access.** Anyone who can push a branch can deploy to your live site and read your Firestore data. Only give write access to people you would trust with the Firebase console.
28+
1. **Repository settings.** On GitHub, under **Settings**:
29+
- **Advanced Security**: turn on Dependabot alerts, secret scanning, push protection and private vulnerability reporting.
30+
- **Actions** > **General**: set the workflow permissions to read, and turn off "Allow GitHub Actions to create and approve pull requests" unless you use release-please.
31+
- **Rules**: add a ruleset for `main` that requires a pull request and the `build`, `test` and `lint` checks, and blocks force pushes.
32+
33+
## What `./hb doctor` checks
34+
35+
- Your Node.js version, Firebase login, project and site config.
36+
- No service account key files in the repository root.
37+
- The Blaze plan, and that every function is deployed as 2nd gen.
38+
- The Realtime Database is off.
39+
- The GitHub deploy setup and its roles, as `./hb setup-github` sets them.
40+
- Service account keys, and old `github-action-*` accounts.
41+
- The browser API key and the Maps key: which sites and APIs they allow.
42+
43+
Each problem comes with what to change. Some checks need an API turned on in your project, and say so.
44+
45+
## Reporting a vulnerability
46+
47+
To report a vulnerability in Hoverboard, see the [security policy](../../.github/SECURITY.md).

‎packages/cli/src/commands/doctor.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { findRepoRoot, checkNodeVersion, type DoctorCheckResult } from '../utils/node-version.js';
2+
import { checkApiKeys } from '../utils/api-keys.js';
23
import { checkBilling } from '../utils/billing.js';
34
import { checkFirebaseProject, resolveFirebaseProjectId } from '../utils/firebase-project.js';
45
import { checkFunctions } from '../utils/functions.js';
56
import { checkRealtimeDatabase } from '../utils/realtime-database.js';
67
import { checkServiceAccountKeys } from '../utils/service-account-keys.js';
8+
import { checkServiceAccounts } from '../utils/service-accounts.js';
79
import { checkSiteConfig } from '../utils/site-config.js';
810
import { checkGitHubDeploys } from './setup-github.js';
911

@@ -29,6 +31,8 @@ export const runDoctor = async (): Promise<boolean> => {
2931
await checkFunctions(repoRoot, projectId),
3032
await checkRealtimeDatabase(repoRoot, projectId),
3133
await checkGitHubDeploys(repoRoot, projectId),
34+
await checkServiceAccounts(repoRoot, projectId),
35+
await checkApiKeys(repoRoot, projectId),
3236
];
3337

3438
for (const check of checks) {

0 commit comments

Comments
 (0)