Skip to content

Forms Portal — forms.greenpeace.us

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

Form Definition (YAML example)

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)

Notification Routing (per form, configurable)

  • 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

Departments & Forms (migrated from forms.us.gl3)

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

  1. Export 25 form definitions from PHP system as YAML configs
  2. Map notification trees to new routing config
  3. Set up Cloud SQL instance + schema migration
  4. Deploy frontend + backend to Cloud Run
  5. Parallel run: both systems active for 2 weeks
  6. DNS cutover: forms.greenpeace.us goes live
  7. 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.

Tabletop Exercise — BT-006 Forms Portal Compromise

  • 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

Incident Response — Forms-specific procedures

  • 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

Disaster Recovery — Forms-specific

  • 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.

CIS Controls Mapping for Forms Portal

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.

PCI-DSS Controls Mapping for Forms Portal

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)

MITRE ATT&CK Mapping for Forms Portal

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