Safe Online Exam
Deploy the application

Google Cloud Run + Cloud SQL

Install or upgrade a verified public release with the Cloud Run bundle: Cloud SQL, Secret Manager version pins, staged traffic, and the two Canvas handoffs.

This is the recommended managed deployment path. The versioned Cloud Run bundle installs or upgrades a published image without an application source checkout or Cloud Build. It uses one immutable image digest for the migration job, cleanup job, and application service.

The scripts never print secret values. They pin Secret Manager to the exact numbered versions in .state/secret-versions.env, run migrations before traffic moves, and keep a candidate revision at zero traffic until /ready and the public JWKS endpoint pass.

Requirements

  • A billed Google Cloud project and permission to administer Cloud Run, Cloud SQL, IAM, Secret Manager, Cloud Scheduler, and required APIs.
  • Current gcloud, gh, Docker, jq, OpenSSL, and curl on the operator workstation.
  • A final public HTTPS origin. The bundle can reserve a stable run.app URL; set TOOL_URL only when an approved custom HTTPS origin will front it.
  • A Canvas administrator and a device-management owner for the two external handoffs.

Download and verify the bundle

Use the release notes for the current version and digest:

export VERSION="X.Y.Z"
curl -fLO "https://github.com/JSB2010/safe-online-exam/releases/download/v${VERSION}/safe-online-exam-${VERSION}-cloud-run.tar.gz"
curl -fLO "https://github.com/JSB2010/safe-online-exam/releases/download/v${VERSION}/safe-online-exam-${VERSION}-cloud-run.tar.gz.sha256"
sha256sum --check "safe-online-exam-${VERSION}-cloud-run.tar.gz.sha256"
tar -xzf "safe-online-exam-${VERSION}-cloud-run.tar.gz"
cd "safe-online-exam-${VERSION}-cloud-run"

Authenticate, then run the exact attestation command from the release notes. It must identify JSB2010/safe-online-exam, .github/workflows/publish-release-image.yml, the release commit, and the tag.

Guided installation

Start with the bundle's own README.md, then run:

./setup.sh

The walkthrough performs a read-only preflight, collects a project/region/resource prefix, generates protected bootstrap material, presents the Cloud SQL profile catalog and cost confirmation, provisions or validates resources, and pauses for Canvas at the right times. Sensitive values use no-echo prompts or protected files, not command-line arguments.

Keep the extracted bundle, BOOTSTRAP_DIRECTORY, CLIENT_IDENTITY_DIRECTORY, and STATE_DIRECTORY out of /tmp, /private/tmp, and TMPDIR. The bundle rejects those locations so an operating system cleanup policy cannot remove the only client identity or deployment record. It writes generated secrets without a terminal line ending and rejects a supplied database-password file that contains a line break.

It is safe to resume by stage when different administrators own different parts:

./setup.sh --stage configure
./setup.sh --stage prepare
./setup.sh --stage install
./setup.sh --stage finalize

For unattended use, follow the exact --non-interactive file-input examples in the extracted README. Do not turn secrets into shell arguments.

What a new installation creates

With RESOURCE_NAME=safe-online-exam, defaults are:

ResourceDefault
Cloud Run service and Cloud SQL instancesafe-online-exam
Migration and cleanup jobssafe-online-exam-migrate, safe-online-exam-cleanup
Runtime and scheduler service accountssafe-online-exam, safe-online-exam-sched
PostgreSQL database and usersafe_online_exam
Secret Manager prefixsafe_online_exam_

Use a short, school-specific suffix for separate installations in one project. Once Canvas is configured, keep the service name, project, region, and public URL stable. These portable defaults do not rename the repository's existing canvas-seb-dev or canvas-seb-prod source-build environments.

Cloud SQL and public access

production-zonal is the bundle's cost-conscious new-install default: Cloud SQL PostgreSQL 17 Enterprise, one dedicated vCPU, 3.75 GiB, one zone, daily backups, 14 retained backups, seven-day PITR, storage auto-growth, deletion protection, and connector-only access. It trades automatic cross-zone failover and the HA SLA for a lower bill. Use ./prepare.sh --list-cloud-sql-profiles before accepting a billable resource, and check current Google Cloud pricing and terms.

existing-reviewed never creates an instance; the institution owns its regional, HA, backup, retention, networking, capacity, and deletion-protection review.

On a newly configured project, the bundle waits with bounded retries for the Cloud SQL Admin API to propagate and for a new instance to become RUNNABLE. If either wait expires, inspect the existing Google Cloud operation before rerunning prepare.sh; do not assume a second create request is safe.

Canvas must reach the tool without a Google login. PUBLIC_ACCESS=true grants roles/run.invoker to allUsers on the application service only. If policy forbids it, put an approved public HTTPS load balancer in front, set TOOL_URL to that stable origin, and keep migration and cleanup jobs non-public.

The two Canvas handoffs

After the URL is reserved, the installer stops for the Canvas API OAuth Developer Key. It then installs a first revision with bootstrap-pending LTI values. Create the LTI key using ${TOOL_URL}/lti/config, install the app, and run the final stage with the actual client and deployment IDs. The final stage creates new numbered versions only for those LTI identifiers, updates jobs, stages a revision, checks it, and explicitly cuts traffic over.

Do not upload the generated SEB .p12, private key, or password to Cloud Run or Secret Manager. Move the client-only identity directory into the approved MDM or vault workflow.

Custom domain and generated Cloud Run URL

When the final public origin uses Cloud Run domain mapping, set TOOL_URL to that HTTPS origin before the prepare stage. After the service is prepared, run:

./map-domain.sh cloudrun.env

The helper creates or inspects the mapping and reports its DNS records and Ready conditions; it does not change external DNS. Complete domain ownership and DNS, then verify the custom origin's health, readiness, JWKS, and LTI endpoints. Cloud Run domain mapping is a Preview feature, so use an approved load balancer when that is not an acceptable operational dependency.

Leave the generated run.app URL enabled while the installation is finalized. Set DISABLE_DEFAULT_URL_AFTER_FINALIZE=true only after the custom origin passes those checks. The final stage verifies its zero-traffic candidate first, cuts over, rechecks the custom origin, and then disables the generated URL. Later upgrades preserve that policy: they verify the custom origin, temporarily restore the generated URL for tagged candidate checks, cut over, and disable it again even if the candidate exits early.

Generate and upload the Canvas theme loader

After prepare.sh, have the bundle render the exact loader for the configured public origin:

./canvas-theme-loader.sh cloudrun.env

Upload the generated canvas-theme-loader.js as the active Canvas account or sub-account theme's desktop JavaScript before testing a protected assessment. It contains no secret material. This theme step is separate from LTI registration; see Load the detector script for the Canvas-side procedure.

Upgrade and rollback

Download and verify the next bundle. Preserve cloudrun.env, the bootstrap directory, .state, and the client identity records; merge new template keys and replace only APP_VERSION and APP_IMAGE with the new bundle values.

./upgrade.sh cloudrun.env

The helper creates an on-demand Cloud SQL backup, waits for its operation to finish, and requires the backup status to be SUCCESSFUL before migrations or traffic changes. It records the prior traffic revision, migrates, updates cleanup, deploys a no-traffic candidate, checks /ready and JWKS, then moves 100% traffic. When the generated URL was disabled, it verifies the custom origin, temporarily enables that URL for the candidate, then verifies the custom origin again and restores the disabled-URL policy. If a candidate check fails, the prior revision keeps traffic and guarded cleanup restores the prior URL policy; completed forward migrations remain.

Application rollback needs the protected rollback record and an explicit schema-compatibility confirmation. It verifies TOOL_URL when a custom origin is configured. Restore a backup into a controlled target for schema or data recovery; never down-migrate or overwrite the active database as a first response.

Post-deployment checks

curl -fsS "$TOOL_URL/health"
curl -fsS "$TOOL_URL/ready"
curl -fsS "$TOOL_URL/.well-known/jwks.json"
curl -fsS "$TOOL_URL/lti/config"
curl -fsS "$TOOL_URL/js/canvas-seb-detector.js"

Also inspect the active revision and digest, migration and cleanup executions, Cloud Scheduler, backup/PITR state, numbered secret references, and application errors. These route checks do not replace the role-based Canvas and SEB acceptance run.

On this page