Document version
v1.3 — 2026-04-18 (Phase 1 implementation notes)
Overview
Replacement for the legacy PHP forms system at forms.us.gl3. Cloud Run hosted, Okta SSO protected, PostgreSQL backed internal forms portal for Greenpeace USA. Handles ~25 forms across 9 departments (HR, IT, Finance, Facilities, Research, Communications, PMO, Data Team, Supporter Care) with configurable email/ticket/Slack notification routing.
Phase 1 implementation status (2026-04-18)
Phase 1 lands the backend, Cloud SQL, encryption, Okta wiring, and monitoring. Phase 2 delivers the React frontend. Phase 3 wires HappyFox/email/Slack routing and full report pipelines. Detailed step-by-step operator instructions live in forms-backend/RUNBOOK-CLAUDE-CODE.md (terminal tasks) and forms-backend/RUNBOOK-COWORK.md (console/DNS/Okta/compliance tasks).
| Area |
Phase 1 status |
Notes |
Cloud Run service gpus-forms-backend |
Deployed |
us-central1, VPC egress = private-ranges-only, no unauthenticated access |
Cloud SQL gpus-forms-db (PG 15) |
Provisioned |
Private IP only, CMEK gpus-forms-cmek, IAM DB auth, daily backups + PITR |
| Encryption — envelope AES-256-GCM |
Implemented in crypto.py |
Per-submission DEK wrapped by KMS key gpus-forms-dek-wrapper |
| CMEK keys |
Created and rotating |
gpus-forms-cmek + gpus-forms-dek-wrapper — 90-day auto-rotation |
| Secret Manager |
Populated |
gpus-forms-happyfox-api-key, gpus-forms-happyfox-auth-code, gpus-formfeed-mysql-password — Accessor = gpus-forms-backend@gpus-infra.iam.gserviceaccount.com |
forms.greenpeace.us DNS / custom domain |
Pending — Task 3 of Cowork runbook |
Cloud Run domain mapping + managed cert once DNS record lands |
| Okta OIDC app (redirect URIs) |
Pending — Task 4 of Cowork runbook |
Add redirect + sign-out URIs to Greenpeace.US SPA app |
Cloud Armor policy gpus-forms-armor |
Pending — Task 5 (after Task 3) |
OWASP + SQLi/XSS/RFI/LFI in log mode first; rate-limit 60/min/IP |
| Wazuh rules 100020–100025 |
Deployed on MAPLE |
Rule file: forms-backend/wazuh-rules/gpus-forms-portal-rules.xml |
| Prometheus alerts |
Deployed on SUN/MAPLE |
Source: forms-backend/grafana/prometheus-alerts.yml |
| Grafana dashboard |
Imported |
Source: forms-backend/grafana/dashboard.json |
| Cloud Build trigger |
Manual for first build |
Flip to auto after first successful build — Cowork Task 12 |
| Phase 1.5 legacy-MySQL migration |
In progress |
forms-backend/migrate/ — see addendum runbook |
Phase 1 end-to-end readiness checklist
End-to-end smoke test (Phase 2 opens this up for real users) is blocked until DNS (Task 3) and Okta (Task 4) are complete. Before first real-user submission, Cowork Tasks 6 (compliance sign-off), 9 (IRP + pentest), and 10 (docs + IAR) must also be closed. The remaining Phase 1 tasks (7 backup verification, 8 quarterly rotation, 11 UAT, 13 monthly report cron) can defer past cutover.
Architecture
- Frontend: React SPA on Cloud Run (
gpus-forms-site), dark theme matching existing portals
- Backend: Flask API on Cloud Run (
gpus-forms-backend)
- Database: Cloud SQL PostgreSQL (
us-central1, private IP via VPC connector gpus-vpc-connector)
- Auth: Okta SSO (OIDC PKCE, shared module
gpus-okta-auth.js), Phase 2 backend JWT validation
- Form definitions: YAML files in git repo (source of truth) + admin UI for convenience
- CI/CD: git push → Cloud Build → auto-deploy (same pattern as all portals)
- DNS:
forms.greenpeace.us via Hover
Design System
- Background:
#0A0B10 (primary), #1A1B2E (cards)
- Accent:
#7C5CFC (purple, primary actions), #22D3EE (cyan, info), #34D399 (green, success), #F87171 (red, errors), #FBBF24 (yellow, warnings), #FB923C (orange, highlights)
- Text:
#E8EAF0 (primary), #9094A6 (secondary), #5C6078 (muted)
- Borders:
#2A2D3E
- Typography: Trebuchet MS (headings), Calibri (body), Consolas/JetBrains Mono (code/IDs)
- Cards: dark background with colored left accent borders (same pattern as SOC dashboard)
- Form inputs: dark background
#12131E, border #2A2D3E, focus border accent purple
Database Schema (PostgreSQL)
| Table |
Columns |
forms |
id, slug, title, department, fields (JSONB), notification_rules (JSONB), status, version, created_at, updated_at |
submissions |
id, form_id (FK), submitted_by, submitted_by_email, data (JSONB), status (draft/submitted/processed/archived), created_at |
notifications |
id, submission_id (FK), channel (email/happyfox/slack), recipient, template_name, status (pending/sent/failed), sent_at, error_message |
templates |
id, form_id (FK), name, subject_template, body_template (Jinja2), recipient_groups |
users |
id, email, name, department, role (admin/manager/staff), synced from Okta |
audit_log |
id, user_id, action, entity_type, entity_id, details (JSONB), created_at |
slug: new-employee
title: New Employee Notification
department: Human Resources
fields:
- name: first_name
label: "First Name (Given Name)"
type: text
required: true
- name: last_name
label: "Last Name (Surname)"
type: text
required: true
- name: dol_status
label: DOL Status
type: select
options: ["Full-Time/Regular", "Volunteer", "Temporary/Part-Time", "Intern", "Fellow", "Contractor"]
required: true
- name: start_date
label: Start Date
type: date
required: true
- name: location
label: Location
type: select
options: ["DC", "OAK", "SED", "OAK Warehouse", "MD Warehouse", "Other"]
- name: notes_it
label: Notes for IT
type: textarea
notifications:
- template: general-new-employee
recipients: ["gpus-it-accounts@greenpeace.org", "gpus-facilities@greenpeace.org"]
- template: hr-new-employee
recipients: ["gpus-people@greenpeace.org"]
- channel: happyfox
queue: IT Onboarding
priority: medium
Supported Field Types
text, textarea, email, phone, number, date, select (dropdown), multi-select, checkbox, radio, file-upload, section-header (visual grouping)
- Email: individual addresses or distribution groups via Postfix on MAPLE (relay through Gmail)
- HappyFox: auto-create ticket in specified queue with priority and category
- Slack: post to specified channel via incoming webhook
- Each form defines its own routing rules in the YAML config
- Notification tree view in admin UI (mirrors the legacy notification tree)
Reports & Admin
- Submission history: filterable by form, department, date range, status
- Export: CSV and PDF
- Dashboard: submissions per department, per form, per week/month (Chart.js)
- Admin form builder: drag-and-drop field ordering, field type picker, validation rules, preview mode
- Notification editor: add/remove recipients, edit templates, configure HappyFox queues
- YAML sync: admin can trigger reload from git config files
- Role-based access: admin (IT — full access), manager (view own department submissions), staff (submit only)
Security
- Okta SSO (OIDC PKCE + backend JWT validation)
- All data encrypted at rest (Cloud SQL managed encryption)
- Audit log for every action (submit, view, edit, export, admin changes)
- VPC connector for private Cloud SQL access
- CORS restricted to
forms.greenpeace.us
- Rate limiting on submission endpoints
- File upload scanning (if file-upload fields used)
- CIS-aligned controls matching all other Cloud Run services
| Department |
Count |
Forms |
| Human Resources |
11 |
New Employee, Termination, Contract Extension, Address Change, Office Transfer, Manager Change, Rate/Position Change, Laptop Retrieval, HR Support, Office Visitors, New Employee Contractor/Intern |
| Information Technology |
5 |
IT Support Request, DHCP Request, Dynamic DNS, Data Restoration, Office Visitors |
| Finance |
1 |
Finance Support Request |
| Facilities |
2 |
Facilities Support, Office Visitors |
| Research |
1 |
Research Request |
| Communications |
2 |
Photo Licensing, Video Licensing |
| PMO |
1 |
Project Planning Facilitation |
| Data Team |
5 |
Data Analysis, Data Request, Database Account Access, Database Support, Database Training |
| Supporter Care |
2 |
Frontline Form, Refund Processing |
Migration Plan
- Export 25 form definitions from PHP system as YAML configs
- Map notification trees to new routing config
- Set up Cloud SQL instance + schema migration
- Deploy frontend + backend to Cloud Run
- Parallel run: both systems active for 2 weeks
- DNS cutover:
forms.greenpeace.us goes live
- Retire
forms.us.gl3
GCP Resources (planned)
- Cloud SQL:
gpus-forms-db (PostgreSQL 15, db-f1-micro initially, us-central1)
- Cloud Run:
gpus-forms-site, gpus-forms-backend
- Artifact Registry:
gpus-images/forms-site, gpus-images/forms-backend
- VPC connector: shared
gpus-vpc-connector (10.8.0.0/28)
- DNS:
forms.greenpeace.us → Cloud Run custom domain
Backup Strategy
- Cloud SQL automated backups: daily, 7-day retention, point-in-time recovery enabled
- Weekly export to GCS:
gs://gpus-infra-backups-wdc/forms-db/YYYY-MM-DD/ (pg_dump SQL format)
- Backup script on MAPLE:
/opt/gpus-reports/forms-db-backup.sh (cron weekly Sunday 3:00 AM UTC)
- Backup verification: monthly restore test to a temporary Cloud SQL instance
- Recovery Time Objective (RTO): 1 hour, Recovery Point Objective (RPO): 24 hours
Portal Integration
Updates needed when forms portal goes live:
- status.greenpeace.us: Add Forms Server card to Operations tab (Cloud SQL health, submission count 24h, last backup timestamp). Add to Backups tab (Cloud SQL backup status alongside WDC GCS backups). Add to Executive tab KPIs.
- soc.greenpeace.us: Add Forms to Wazuh monitoring scope (Cloud SQL audit logs → CEDAR ES). Add to Compliance tab (CIS/PCI-DSS controls for database). Add Cloud SQL metrics to System Metrics tab.
- infra.greenpeace.us: Architecture diagram update to include
forms.greenpeace.us. New server runbook page for Cloud SQL instance management.
- Scenario: SQL injection attempt detected on forms-backend, attacker gains read access to submissions table containing HR data (employee PII, salary data)
- Type: Tabletop, Duration: 2 hours, Owner: IT Manager
- Participants: IT Admin, IT Manager, HR, Legal, Data Protection
- Discussion injects:
- T+0: Wazuh alert on suspicious SQL query pattern
- T+15: Cloud SQL audit log shows unauthorized SELECT on submissions table
- T+30: Scope assessment — which forms contain PII?
- T+45: Containment — revoke VPC connector, rotate credentials
- T+60: Legal/notification obligations (employee data breach)
- T+90: Recovery and hardening
- Success criteria: Wazuh detection within 5 minutes, containment within 30 minutes, breach notification decision documented, post-incident AIDE baseline update, lessons learned within 5 business days
- Add to IRP appendix: Forms Portal incident classification
- P1: PII breach
- P2: service outage
- P3: submission loss
- P4: notification failure
- Evidence preservation: Cloud SQL audit logs, Cloud Run request logs, Wazuh alerts, VPC flow logs
- Containment actions: disable Cloud Run service, revoke VPC connector access, rotate Cloud SQL credentials, enable Cloud SQL SSL enforcement
- Recovery order: Cloud SQL restore → backend redeploy → frontend redeploy → notification test → Okta SSO verify
- Add to DRP: Cloud SQL recovery procedure (automated backup restore, point-in-time recovery, cross-region failover if needed)
- RTO: 1 hour (restore from automated backup), RPO: 24 hours (daily backups), RPO with PITR: 5 minutes
- DR test: quarterly restore from backup to temp instance, verify submission data integrity, verify notification routing
- Failover: If
us-central1 unavailable, restore to us-east1 from GCS export, update VPC connector and DNS
Wazuh Monitoring for Cloud SQL
- Cloud SQL audit logs forwarded to CEDAR ES via Cloud Logging sink
- Custom Wazuh rules: suspicious query patterns (UNION SELECT, DROP TABLE, etc.), failed authentication attempts, privilege escalation, bulk data export
- Alert levels: L12 for SQL injection patterns, L10 for bulk data access, L8 for failed auth
Compliance controls — full mapping
The full implementation-level CIS / PCI-DSS / NIST 800-53 / MITRE ATT&CK mapping (with per-control implementation notes, data classification, and key/encryption inventory) lives in Forms Portal — Compliance Controls. The mapping below is a high-level summary kept here for quick reference; the linked page is the authoritative document and is what the Director of Cyber Security signs off on per Cowork Task 6.
| Control |
Description |
| CIS 1.1 |
Cloud SQL in asset inventory (servers.py, wdchostregistry.csv) |
| CIS 3.1 |
Data classification — submissions containing PII classified as Confidential |
| CIS 5.2 |
Privileged access — Cloud SQL admin via IAM only, no direct password auth |
| CIS 6.1 |
Access control — Okta SSO + role-based access (admin/manager/staff) |
| CIS 8.2 |
Audit logging — Cloud SQL audit logs + Cloud Run request logs |
| CIS 11.1 |
Automated backups — daily Cloud SQL + weekly GCS export |
| CIS 16.7 |
Application security — input validation, parameterized queries, CORS, rate limiting |
Data Encryption
- At rest: Cloud SQL default encryption (AES-256, Google-managed keys). Upgrade path: Customer-Managed Encryption Keys (CMEK) via Cloud KMS if required by policy.
- In transit: TLS 1.3 enforced on all connections — Cloud Run (HTTPS), Cloud SQL (SSL required,
require_ssl=true), VPC connector (private IP, no public endpoint).
- Application-level encryption: PII fields (SSN, salary, personal email, phone, address) encrypted with AES-256-GCM before storage in JSONB. Encryption key in Secret Manager (
gpus-forms-pii-key). Decrypted only on authorized read with audit log entry.
- Backup encryption: Cloud SQL backups encrypted by default. GCS exports use bucket-level encryption (AES-256).
- Key rotation: 90-day rotation schedule for application encryption keys via Cloud KMS/Secret Manager.
| Control |
Description |
| PCI 1.1 |
Network segmentation — Cloud SQL on private IP, VPC connector, no public endpoint, firewall rules restrict to Cloud Run service only |
| PCI 2.1 |
No default credentials — Cloud SQL admin password in Secret Manager, rotated quarterly |
| PCI 3.4 |
PII fields encrypted at rest with AES-256-GCM before storage, rendering stored data unreadable |
| PCI 4.1 |
TLS 1.3 on all data in transit — HTTPS frontend, SSL-required Cloud SQL, encrypted VPC connector |
| PCI 6.5 |
Secure development — parameterized queries (no SQL injection), input validation, output encoding, CSRF protection, Content Security Policy headers |
| PCI 6.6 |
Web Application Firewall — Cloud Armor WAF rules on Cloud Run (OWASP Top 10 rule set) |
| PCI 7.1 |
Least privilege — role-based access (admin/manager/staff), Okta SSO, no shared accounts |
| PCI 8.1 |
Unique user identification — Okta SSO, every action tied to authenticated user email |
| PCI 8.5 |
No shared/group authentication — individual Okta accounts only |
| PCI 10.1 |
Audit trails — Cloud SQL audit logs, Cloud Run request logs, application audit_log table, all linked to user identity |
| PCI 10.2 |
Audit events recorded — login, submission, view, export, admin changes, failed access attempts |
| PCI 10.5 |
Audit log integrity — Cloud SQL audit logs immutable (managed by GCP), application audit_log table append-only (no UPDATE/DELETE permissions for app service account) |
| PCI 11.2 |
Vulnerability scanning — OpenVAS weekly scan includes Cloud Run endpoints, Cloud SQL included in quarterly pen test scope |
| PCI 12.1 |
Security policy — forms portal operations covered under GPUS IT Security Policy |
| PCI 12.10 |
Incident response — forms-specific IR procedures in IRP appendix (see IR section) |
| Technique |
Mitigation |
| T1190 (Exploit Public-Facing Application) |
Cloud Armor WAF, input validation, parameterized queries, rate limiting |
| T1078 (Valid Accounts) |
Okta SSO with MFA, session timeout 30 min, no service account passwords exposed |
| T1110 (Brute Force) |
Okta account lockout after 5 failed attempts, Cloud Run rate limiting |
| T1059 (Command and Script Interpreter) |
No shell execution from form inputs, strict input sanitization |
| T1003 (OS Credential Dumping) |
Cloud SQL managed — no OS access, no credential files on disk |
| T1005 (Data from Local System) |
No local data storage — all submissions in Cloud SQL, no file caching on Cloud Run instances |
| T1530 (Data from Cloud Storage) |
GCS backup bucket IAM restricted to backup SA only, no public access |
| T1537 (Transfer Data to Cloud Account) |
VPC Service Controls (planned), Cloud SQL no public IP |
| T1565 (Data Manipulation) |
Application audit_log tracks all data changes, JSONB field versioning, tamper detection via checksums |
| T1486 (Data Encrypted for Impact / Ransomware) |
Cloud SQL automated backups with point-in-time recovery, GCS exports in separate bucket with object versioning, 30-day retention |
| T1071 (Application Layer Protocol) |
Cloud Armor inspects HTTP traffic, suspicious request patterns trigger Wazuh alerts |
| T1567 (Exfiltration Over Web Service) |
Rate limiting on export endpoints, bulk export requires admin role, audit logged |
NIST CSF Mapping
| Function |
Description |
| Identify (ID.AM) |
Cloud SQL instance in asset inventory, data classification for PII forms |
| Protect (PR.AC) |
Okta SSO, RBAC, encryption at rest and in transit, least privilege IAM |
| Protect (PR.DS) |
PII field-level encryption, TLS 1.3, backup encryption |
| Detect (DE.CM) |
Wazuh monitoring, Cloud SQL audit logs, Cloud Armor alerts, application audit trail |
| Respond (RS.RP) |
Forms-specific IR procedures, containment playbook, breach notification workflow |
| Recover (RC.RP) |
Cloud SQL automated restore, GCS export restore, DR test quarterly |
NIST SP 800-53 Controls
| Control |
Description |
| AC-3 (Access Enforcement) |
Okta RBAC — admin/manager/staff roles enforced at API level |
| AC-6 (Least Privilege) |
Cloud SQL service account has minimal permissions (SELECT/INSERT/UPDATE on app tables only, no DDL) |
| AU-2 (Auditable Events) |
All submissions, views, exports, admin changes, auth events logged |
| AU-6 (Audit Review) |
Cloud SQL audit logs → CEDAR ES → SOC dashboard, weekly review in IT Ops report |
| CM-7 (Least Functionality) |
Cloud Run minimal container — no shell, no SSH, read-only filesystem, single-purpose Flask app |
| IA-2 (Identification & Auth) |
Okta SSO with MFA, JWT validation on backend |
| MP-4 (Media Storage) |
No removable media — all data in Cloud SQL and GCS, encrypted |
| SC-8 (Transmission Confidentiality) |
TLS 1.3 everywhere |
| SC-28 (Protection of Info at Rest) |
AES-256 Cloud SQL encryption + application-level PII encryption |
| SI-4 (System Monitoring) |
Wazuh agents on Cloud Run (via log forwarding), Cloud SQL audit logs, Cloud Armor metrics |
| SI-10 (Information Input Validation) |
Server-side input validation, parameterized queries, type checking, length limits |
ISO 27001 Annex A Controls
| Control |
Description |
| A.8.24 |
Use of cryptography — AES-256-GCM for PII, TLS 1.3, Cloud KMS key management |
| A.8.25 |
Secure development — OWASP secure coding, code review, dependency scanning |
| A.8.26 |
Application security requirements — input validation, authentication, authorization, session management |
| A.5.34 |
Privacy and PII protection — field-level encryption, access controls, audit logging, data minimization |