Skip to content
This repository was archived by the owner on Mar 30, 2026. It is now read-only.

Commit 9cd2923

Browse files
committed
feat: Update environment configuration and documentation for secret management and deployment
1 parent a6961ff commit 9cd2923

10 files changed

Lines changed: 150 additions & 90 deletions

File tree

.env.example

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
# GitHub App settings
22
GITHUB_APP_ID=
3-
GITHUB_PRIVATE_KEY=
4-
GITHUB_WEBHOOK_SECRET=
3+
4+
# Secrets are provided as files.
5+
# Local dev (running the app on your machine): use the tutorial's local-secrets folder.
6+
GITHUB_PRIVATE_KEY_FILE=./local-secrets/github-private-key/private_key
7+
GITHUB_WEBHOOK_SECRET_FILE=./local-secrets/github-webhook-secret/webhook_secret
8+
9+
# Docker/Cloud Run (secrets mounted into the container): these are the in-container paths.
10+
# GITHUB_PRIVATE_KEY_FILE=/var/secrets/github-private-key/private_key
11+
# GITHUB_WEBHOOK_SECRET_FILE=/var/secrets/github-webhook-secret/webhook_secret
512

613
# Optional: limit accepted event types (comma-separated, e.g. pull_request,push)
714
GITHUB_ACCEPTED_EVENTS=

.gitignore

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,10 +195,39 @@ celerybeat.pid
195195

196196
# Environments
197197
.env
198+
.env.*
199+
!.env.example
198200
.venv
199201
env/
200202
venv/
201203

204+
### Local secrets (do not commit) ###
205+
# Used by docs/tutorial/04-deploy-and-verify.md for local Secret Manager parity.
206+
local-secrets/
207+
208+
# Common key/cert bundles that may contain private material.
209+
*.pem
210+
*.key
211+
*.p12
212+
*.pfx
213+
*.jks
214+
*.der
215+
216+
### Terraform state & local working dirs (never commit) ###
217+
**/.terraform/
218+
**/.terraform.lock.hcl
219+
**/terraform.tfstate
220+
**/terraform.tfstate.*
221+
**/crash.log
222+
**/crash.*.log
223+
**/override.tf
224+
**/override.tf.json
225+
**/*_override.tf
226+
**/*_override.tf.json
227+
228+
# Repo uses terraform.tfvars.example as the template; keep local tfvars untracked.
229+
**/terraform.tfvars
230+
202231
# Terraform
203232
*.tfvars
204233
*.tfvars.json

README.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
A GitHub App backend for secure webhook processing and automation, deployed on Google Cloud Run. The app validates GitHub webhook signatures, manages installation tokens, and routes events for further processing. Infrastructure is provisioned with Terraform, and security is enforced via pre-commit hooks and CI scans.
77

8+
If you want to replicate the full setup (GCP remote state, Terraform apply phases, GitHub App creation, deploy + verification), follow the end-to-end tutorial in `docs/tutorial/README.md`.
9+
810
## Features
911

1012
- FastAPI-based webhook handler (`/webhooks/github`)
@@ -34,8 +36,8 @@ A GitHub App backend for secure webhook processing and automation, deployed on G
3436

3537
3. Set required environment variables:
3638
- `GITHUB_APP_ID`
37-
- `GITHUB_PRIVATE_KEY`
38-
- `GITHUB_WEBHOOK_SECRET`
39+
- `GITHUB_PRIVATE_KEY_FILE`
40+
- `GITHUB_WEBHOOK_SECRET_FILE`
3941
- `GITHUB_ACCEPTED_EVENTS` (optional, comma-separated)
4042

4143
## Usage
@@ -51,16 +53,20 @@ A GitHub App backend for secure webhook processing and automation, deployed on G
5153

5254
## Deployment
5355

54-
- Build and deploy with Docker and Google Cloud Build:
55-
- See `cloudbuild.yaml` and `deploy.sh` for build and deployment steps.
56-
- Infrastructure setup:
57-
- See `infra/terraform/README.md` for Terraform instructions.
56+
- Canonical workflow: Terraform-managed deployment (Cloud Run + API Gateway) with Artifact Registry images and secrets mounted from Secret Manager.
57+
- Start here: `docs/tutorial/README.md`
58+
- Terraform module docs: `infra/terraform/README.md`
59+
60+
- Optional helpers:
61+
- `deploy.sh` can build and push a container image to Artifact Registry.
62+
- `cloudbuild.yaml` can build and push an image in Cloud Build.
63+
These helpers intentionally do not provision infra; Terraform remains the source of truth.
5864

5965
## Security
6066

6167
- Pre-commit hooks scan for secrets and large files.
6268
- CI workflow runs Checkov and Trivy scans on every push/PR.
63-
- Sensitive values are managed via environment variables and never committed.
69+
- Sensitive values are managed via Secret Manager (Cloud Run) or local secret files (development) and never committed.
6470

6571
## Project Workflow
6672

cloudbuild.yaml

Lines changed: 4 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,11 @@
11
steps:
22
- name: gcr.io/cloud-builders/docker
3-
args: ["build", "-t", "gcr.io/$PROJECT_ID/${_SERVICE_NAME}:$COMMIT_SHA", "."]
3+
args: ["build", "-t", "${_REGION}-docker.pkg.dev/$PROJECT_ID/${_ARTIFACT_REPO}/${_SERVICE_NAME}:$COMMIT_SHA", "."]
44
- name: gcr.io/cloud-builders/docker
5-
args: ["push", "gcr.io/$PROJECT_ID/${_SERVICE_NAME}:$COMMIT_SHA"]
6-
- name: gcr.io/google.com/cloudsdktool/cloud-sdk
7-
entrypoint: bash
8-
args:
9-
- -eu
10-
- -c
11-
- |
12-
if [[ -z "${_GITHUB_APP_ID}" ]]; then
13-
echo "ERROR: Missing required substitution _GITHUB_APP_ID (your GitHub App ID)."
14-
echo "Set it on your trigger or pass: gcloud builds submit --substitutions=_GITHUB_APP_ID=123456"
15-
exit 1
16-
fi
17-
- name: gcr.io/google.com/cloudsdktool/cloud-sdk
18-
entrypoint: gcloud
19-
args:
20-
- run
21-
- deploy
22-
- "${_SERVICE_NAME}"
23-
- --image
24-
- "gcr.io/$PROJECT_ID/${_SERVICE_NAME}:$COMMIT_SHA"
25-
- --region
26-
- "${_REGION}"
27-
- --platform
28-
- managed
29-
- --set-env-vars
30-
- "GITHUB_APP_ID=${_GITHUB_APP_ID},GITHUB_ACCEPTED_EVENTS=pull_request,GITHUB_PRIVATE_KEY_FILE=/var/secrets/github_private_key,GITHUB_WEBHOOK_SECRET_FILE=/var/secrets/github_webhook_secret"
31-
- --secret
32-
- github-app-private-key=/var/secrets/github_private_key
33-
- --secret
34-
- github-webhook-secret=/var/secrets/github_webhook_secret
5+
args: ["push", "${_REGION}-docker.pkg.dev/$PROJECT_ID/${_ARTIFACT_REPO}/${_SERVICE_NAME}:$COMMIT_SHA"]
356
substitutions:
367
_SERVICE_NAME: ons-github-app
378
_REGION: europe-west2
38-
_GITHUB_APP_ID: ""
9+
_ARTIFACT_REPO: ons-github-app
3910
images:
40-
- "gcr.io/$PROJECT_ID/${_SERVICE_NAME}:$COMMIT_SHA"
11+
- "${_REGION}-docker.pkg.dev/$PROJECT_ID/${_ARTIFACT_REPO}/${_SERVICE_NAME}:$COMMIT_SHA"

deploy.sh

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,23 @@
22
set -euo pipefail
33

44
: "${PROJECT_ID:?Set PROJECT_ID}"
5-
: "${SERVICE_NAME:=ons-github-app}"
65
: "${REGION:=europe-west2}"
7-
: "${GITHUB_APP_ID:?Set GITHUB_APP_ID}"
6+
: "${ARTIFACT_REPO:=ons-github-app}"
7+
: "${SERVICE_NAME:=ons-github-app}"
8+
: "${TAG:=latest}"
89

9-
: "${GITHUB_ACCEPTED_EVENTS:=pull_request}"
10+
# This repo's canonical deploy path is Terraform (Cloud Run + API Gateway).
11+
# This script is a convenience to build and push the container image to Artifact Registry.
12+
#
13+
# After running, set the resulting image URI as `image = "..."` in `infra/terraform/terraform.tfvars`
14+
# and run `terraform apply`.
1015

11-
IMAGE="gcr.io/${PROJECT_ID}/${SERVICE_NAME}:latest"
16+
IMAGE="${REGION}-docker.pkg.dev/${PROJECT_ID}/${ARTIFACT_REPO}/${SERVICE_NAME}:${TAG}"
1217

13-
gcloud builds submit --tag "${IMAGE}" .
18+
docker buildx build --platform linux/amd64 \
19+
-t "${IMAGE}" \
20+
--push .
1421

15-
gcloud run deploy "${SERVICE_NAME}" \
16-
--image "${IMAGE}" \
17-
--region "${REGION}" \
18-
--platform managed \
19-
--set-env-vars "GITHUB_APP_ID=${GITHUB_APP_ID},GITHUB_ACCEPTED_EVENTS=${GITHUB_ACCEPTED_EVENTS},GITHUB_PRIVATE_KEY_FILE=/var/secrets/github_private_key,GITHUB_WEBHOOK_SECRET_FILE=/var/secrets/github_webhook_secret" \
20-
--secret github-app-private-key=/var/secrets/github_private_key \
21-
--secret github-webhook-secret=/var/secrets/github_webhook_secret
22+
echo
23+
echo "Pushed image: ${IMAGE}"
24+
echo "Next: update infra/terraform/terraform.tfvars (image = \"${IMAGE}\") and run terraform apply"

docs/tutorial/01-prereqs.md

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,18 @@ Recommended:
3636

3737
### Secrets strategy (best practice)
3838

39-
- **Local dev**: you can pass secrets via environment variables (e.g. `docker run --env-file .env ...`).
40-
- **Cloud Run**: secrets should not be plain env vars.
41-
- Store secret values in **Secret Manager**.
42-
- Mount them into the container as **files**.
43-
- The app reads them via:
44-
- `GITHUB_PRIVATE_KEY_FILE`
45-
- `GITHUB_WEBHOOK_SECRET_FILE`
39+
This repo documents a single secrets strategy for both local development and Cloud Run:
40+
41+
- Secrets are stored as **files**.
42+
- The app is configured with **non-secret** environment variables that point at those files:
43+
- `GITHUB_PRIVATE_KEY_FILE`
44+
- `GITHUB_WEBHOOK_SECRET_FILE`
45+
46+
Local development:
47+
- Create a `./local-secrets/` folder (ignored by git) and point the `*_FILE` variables at it.
48+
49+
Cloud Run:
50+
- Store secret values in **Secret Manager** and mount them into the container as **files**.
4651

4752
This keeps secret values out of container images, build logs, and Terraform state.
4853

docs/tutorial/04-deploy-and-verify.md

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ Edit `terraform.tfvars`:
4141

4242
```bash
4343
cd infra/terraform
44-
terraform init
4544
terraform apply
4645
```
4746

@@ -57,55 +56,80 @@ What you should get after phase 1:
5756

5857
This is intentionally **outside Terraform** so secret values do not end up in Terraform state.
5958

59+
This tutorial uses a single approach: secrets are first stored as local files under `./local-secrets/`, then:
60+
61+
- those same files are used for local runs
62+
- and their contents are uploaded to Secret Manager (as secret versions)
63+
64+
Create the local secret files now:
65+
66+
```bash
67+
mkdir -p ./local-secrets/github-private-key ./local-secrets/github-webhook-secret
68+
cp /path/to/private-key.pem ./local-secrets/github-private-key/private_key
69+
printf "%s" "<your-webhook-secret>" > ./local-secrets/github-webhook-secret/webhook_secret
70+
```
71+
6072
### 2.1 Webhook secret
6173

6274
```bash
63-
echo -n "<your-webhook-secret>" | gcloud secrets versions add github-webhook-secret --data-file=-
75+
gcloud secrets versions add github-webhook-secret --data-file=./local-secrets/github-webhook-secret/webhook_secret
6476
```
6577

6678
### 2.2 Private key
6779

6880
```bash
69-
gcloud secrets versions add github-private-key --data-file=/path/to/private-key.pem
81+
gcloud secrets versions add github-private-key --data-file=./local-secrets/github-private-key/private_key
7082
```
7183

7284
## 3) Test locally (before deploying)
7385

74-
### Option A: Local env vars (simple)
86+
This repo assumes a single local strategy: secrets are stored as files in `./local-secrets/` and the app reads them via `GITHUB_PRIVATE_KEY_FILE` and `GITHUB_WEBHOOK_SECRET_FILE`.
87+
88+
You created `./local-secrets/` in step 2; the commands below read from those files.
89+
90+
### 3.1 Run locally (no Docker)
7591

7692
Create a `.env` (do not commit):
7793

7894
```env
7995
GITHUB_APP_ID=123456
80-
GITHUB_PRIVATE_KEY=<paste-private-key-as-a-single-line-with-literal-\n-escapes>
81-
GITHUB_WEBHOOK_SECRET=<your-webhook-secret>
96+
GITHUB_PRIVATE_KEY_FILE=./local-secrets/github-private-key/private_key
97+
GITHUB_WEBHOOK_SECRET_FILE=./local-secrets/github-webhook-secret/webhook_secret
8298
GITHUB_ACCEPTED_EVENTS=pull_request
8399
```
84100

85-
Run:
101+
Load it into your shell and run the API:
102+
103+
```bash
104+
set -a
105+
source .env
106+
set +a
107+
108+
uvicorn src.app:app --host 0.0.0.0 --port 8080
109+
```
110+
111+
In another terminal:
86112

87113
```bash
88-
docker build -t ons-github-app:local .
89-
docker run --rm -p 8080:8080 --env-file .env ons-github-app:local
90114
curl http://localhost:8080/healthz
91115
```
92116

93-
### Option B: Local secret *files* (parity with Cloud Run)
117+
### 3.2 Run in Docker (parity with Cloud Run)
94118

95-
This mirrors how Cloud Run runs the container.
119+
This mirrors how Cloud Run runs the container by mounting `./local-secrets` into the container at `/var/secrets`.
96120

97121
```bash
98-
mkdir -p ./local-secrets
99-
printf "%s" "<your-webhook-secret>" > ./local-secrets/github_webhook_secret
100-
cp /path/to/private-key.pem ./local-secrets/github_private_key
122+
docker build -t ons-github-app:local .
101123

102124
docker run --rm -p 8080:8080 \
103125
-e GITHUB_APP_ID=123456 \
104126
-e GITHUB_ACCEPTED_EVENTS=pull_request \
105-
-e GITHUB_PRIVATE_KEY_FILE=/var/secrets/github_private_key \
106-
-e GITHUB_WEBHOOK_SECRET_FILE=/var/secrets/github_webhook_secret \
127+
-e GITHUB_PRIVATE_KEY_FILE=/var/secrets/github-private-key/private_key \
128+
-e GITHUB_WEBHOOK_SECRET_FILE=/var/secrets/github-webhook-secret/webhook_secret \
107129
-v "${PWD}/local-secrets:/var/secrets:ro" \
108130
ons-github-app:local
131+
132+
curl http://localhost:8080/healthz
109133
```
110134

111135
## 4) Build and push the image

infra/terraform/README.md

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,17 @@ This folder provisions all required Google Cloud resources for the ons-github-ap
1818

1919
2. Edit `terraform.tfvars` to set your project, region, and (optionally) the image URI.
2020

21-
3. Initialize and apply the Terraform configuration:
21+
3. Initialize the Terraform configuration.
22+
23+
This module uses a GCS backend which is intentionally configured at init time (bucket names are project-specific):
24+
25+
```bash
26+
terraform init -backend-config="bucket=<state_bucket_name>"
27+
```
28+
29+
4. Apply the Terraform configuration:
2230

2331
```bash
24-
terraform init
2532
terraform apply
2633
```
2734

@@ -44,6 +51,8 @@ This folder provisions all required Google Cloud resources for the ons-github-ap
4451
1) First apply with `image = ""` provisions APIs, service account, Artifact Registry, and Secret Manager secrets.
4552
2) After pushing an image and adding secret versions, set `image` and apply again to create Cloud Run + API Gateway.
4653

54+
For the end-to-end walkthrough (including remote state bootstrap and GitHub App setup), see `docs/tutorial/README.md`.
55+
4756
## Outputs
4857

4958
After a successful apply, Terraform will output:

infra/terraform/main.tf

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,21 @@ locals {
5353

5454
# Enables required GCP APIs for Cloud Run, Artifact Registry, IAM, and API Gateway
5555
resource "google_project_service" "services" {
56-
for_each = toset(var.services)
57-
project = var.project_id
58-
service = each.value
59-
disable_on_destroy = true
56+
for_each = toset(var.services)
57+
project = var.project_id
58+
service = each.value
59+
# Disabling APIs on destroy can break Terraform refresh/destroy of remaining
60+
# resources (e.g. API Gateway returning 403 if apigateway.googleapis.com is disabled).
61+
# Keep services enabled to make teardown reliable and repeatable.
62+
disable_on_destroy = false
6063
}
6164

6265
# Service account for the Cloud Run app
6366
resource "google_service_account" "app" {
6467
account_id = "${var.service_name}-sa"
6568
display_name = "${var.service_name} service account"
69+
70+
depends_on = [google_project_service.services]
6671
}
6772

6873
resource "google_kms_crypto_key_iam_member" "crypto_key" {
@@ -165,6 +170,8 @@ resource "google_api_gateway_api" "webhook" {
165170
provider = google-beta
166171
api_id = "${var.service_name}-webhook"
167172
project = var.project_id
173+
174+
depends_on = [google_project_service.services]
168175
}
169176

170177
# API Gateway config using OpenAPI spec (api-config.yaml)
@@ -218,6 +225,8 @@ resource "google_secret_manager_secret" "github_private_key" {
218225
}
219226
}
220227
}
228+
229+
depends_on = [google_project_service.services]
221230
}
222231

223232
resource "google_secret_manager_secret_iam_member" "github_private_key_accessor" {
@@ -238,6 +247,8 @@ resource "google_secret_manager_secret" "github_webhook_secret" {
238247
}
239248
}
240249
}
250+
251+
depends_on = [google_project_service.services]
241252
}
242253

243254
resource "google_secret_manager_secret_iam_member" "github_webhook_secret_accessor" {

0 commit comments

Comments
 (0)