Many App Engine applications have run quietly for a decade, still using the project's default service account, open ingress and a runtime that has passed its end of support date. App Engine handles the servers, patching of supported runtimes and TLS for you; the identity each version runs as, who can deploy, which networks and users can reach the app, and when to move off an old runtime are the customer's settings. This guide covers those controls for the App Engine standard environment: service accounts, deployer roles, the App Engine firewall, ingress, Identity-Aware Proxy, secrets, logging and runtime lifecycle planning. It is for platform engineers, application owners and cloud security reviewers who look after long-lived App Engine estates. For newer serverless services on Google Cloud, see the Cloud Run and Cloud Functions Security Guide.
This is an independent guide from NHI Mgmt Group, based on Google Cloud's public documentation as of November 2026. It is not sponsored or reviewed by Google. Product features change, so confirm settings against Google Cloud's current documentation.
Key takeaways
- Give each version its own service account. Set
service_accountinapp.yaml(or on deploy) to a user-managed account with only the roles the app needs, instead of the default[email protected]. - Stop automatic Editor grants. Enforce
iam.automaticIamGrantsForDefaultServiceAccountsand replace Editor on existing default accounts with narrow roles, checking impact with Policy Simulator first. - Deploy rights include actAs. Deploying needs Service Account User on the app's service account, so whoever can deploy can run code as that identity. Grant Deployer plus actAs on one account, not project-wide.
- Layer firewall, ingress and IAP. The firewall filters by IP range, ingress filters by network origin, and IAP checks the user's identity. Internal apps want all three.
- Plan runtime moves against published dates. Legacy runtimes (Python 2.7, Java 8, Go 1.11, PHP 5.5) are deprecated and scheduled for decommissioning on 31 January 2027, after which apps may be disabled.
How identity and access work in App Engine
App Engine involves three identities: the people and pipelines that administer and deploy, the service account each version runs as, and the users or services that call the app. All are Google Cloud IAM principals except anonymous internet callers.
| Identity | Default | What it reaches | Recommended setting |
|---|---|---|---|
| Runtime identity | App Engine default service account [email protected], created on first use | Every API the account's roles allow; depending on organisation policy it may hold Editor on the project | A user-managed service account per app or version, set with service_account in app.yaml |
| Deployer | Whoever holds App Engine Admin or Editor-type roles | New versions, traffic, settings | App Engine Deployer plus Service Account User on the one runtime account |
| Caller | Anyone on the internet (ingress accepts all origins by default) | Every public URL, including version-specific -dot- URLs | Firewall rules, ingress setting and IAP for internal apps |
Per-version service accounts have three documented rules: the account must be in the same project, it can be set only when deploying a new version, and a value given on the gcloud command line takes precedence over app.yaml. Use that to move an app off the default identity one version at a time: deploy a new version with the user-managed account, test it with a version-specific URL, then migrate traffic.
For organisations created after 3 May 2024, Google enforces iam.automaticIamGrantsForDefaultServiceAccounts by default, so new default accounts no longer receive Editor automatically. Older organisations should enforce it themselves and then right-size existing grants. See the Google Cloud IAM and Service Accounts Guide and the Google Cloud Organization Policy Guide.
Admin roles and privileged access
App Engine has a fine-grained set of predefined roles. Two titles are reused for different role IDs, so always work from the ID.
| Role ID | Title | What it allows | Use for |
|---|---|---|---|
roles/appengine.admin | App Engine Service Admin | Full access to App Engine resources | Break-glass platform administrators |
roles/appengine.appAdmin | App Engine Admin | All application configuration, traffic, versions, instances, dispatch rules, disabling the app | Platform team |
roles/appengine.deployer | App Engine Deployer | Read configuration, create versions, delete versions not receiving traffic | CI/CD service accounts |
roles/appengine.serviceAdmin | App Engine Service Admin | Service and version settings, traffic splits and migration; no new deploys or application-level changes | Release managers |
roles/appengine.appViewer | App Engine Viewer | Read-only configuration and settings | Auditors, support |
roles/appengine.codeViewer | App Engine Code Viewer | Viewer rights plus deployed source code | Only where source review is needed |
roles/appengine.appCreator | App Engine Creator | Create the App Engine application for a project | Project provisioning only |
Google's recommended deploy-only setup is App Engine Deployer plus Service Account User (roles/iam.serviceAccountUser) on the app's service account, with Cloud Build Editor and Storage Object Admin added when deploying through gcloud. Treat that bundle as privileged:
- Scope actAs to one account: grant Service Account User on the specific runtime service account, never at project level, or a deployer can run code as any service account in the project.
- Split deploy from traffic: Deployer creates versions but cannot migrate traffic; Service Admin moves traffic but cannot deploy. Giving them to different identities adds a review step before code takes production traffic.
- Restrict Code Viewer: deployed source often includes configuration; grant it only where needed.
- Protect the firewall: firewall changes need App Engine Admin, Editor or Owner. Keep basic roles out of production projects so firewall changes stay with the platform team.
Integrations, API tokens and service accounts
Long-lived App Engine apps often accumulate non-human credentials: service account keys added years ago, API keys in configuration, and default credentials used to reach other projects. Bring each one under a named identity.
- Calling Google APIs: use the version's service account through Application Default Credentials. There is no need for key files inside the deployment. If an old app ships a JSON key, replace it; see the guide to replacing Google Cloud service account keys.
- Secrets: environment variables in
app.yamlare part of the deployed configuration and readable by anyone who can view it. Store passwords and tokens in Secret Manager, grantroles/secretmanager.secretAccessoron each secret to the runtime service account, and read them at start-up. See the Secrets Management Guide. - Pub/Sub push into App Engine: use an authenticated push subscription. For an app behind IAP, Google documents granting the push auth service account the IAP-secured Web App User role and using the IAP client ID as the token audience. See Google Cloud Pub/Sub Security.
- Outbound calls to private systems: by default only traffic to private IP addresses goes through your VPC (Direct VPC egress or a Serverless VPC Access connector). Set egress to
all-trafficwith Cloud NAT and static IPs when partners allowlist your source address, because default outbound traffic uses a shared regional pool. - CI/CD: run deployments from a dedicated service account, federated from your CI system with Workload Identity Federation rather than a stored key. See the Cloud Workload Identity Guide.
Protecting the front door
| Control | What it checks | Default | Key settings |
|---|---|---|---|
| App Engine firewall | Source IP range | A default rule at priority 2147483647 covering all IPs, evaluated last | Rules at priority 1 to 2147483646 with allow or deny (deny returns 403); gcloud app firewall-rules create, test-ip, list |
| Ingress setting | Network origin of the request | Accepts traffic from all origins | Restrict to internal traffic, or internal plus Cloud Load Balancing, for apps fronted by a load balancer |
| Identity-Aware Proxy | The caller's Google identity | Off | gcloud iap web enable --resource-type=app-engine; grant roles/iap.httpsResourceAccessor to groups |
| HTTPS | Transport | HTTPS available on appspot.com URLs | Managed SSL certificates for custom domains; set secure: always on handlers to redirect HTTP |
For internal apps, turn on IAP and grant access to groups only; Owner and Editor do not automatically pass IAP, which keeps administration and application access separate. Set the firewall default rule to deny once explicit allow rules cover the ranges you need (for example a corporate range during testing, or only the ranges a WAF forwards from). Remember version-specific URLs (VERSION-dot-SERVICE-dot-PROJECT_ID.REGION_ID.r.appspot.com): firewall, ingress and IAP apply to them too, which is why they are the right layer rather than in-app checks on the default URL. See the Identity-Aware Proxy Guide.
For the standard environment, Google's security overview positions the App Engine firewall, ingress controls and IAP as the network boundary for the app. Place VPC Service Controls perimeters around the data services the app calls, such as Cloud Storage and BigQuery, and allow the app's service account through them. See the VPC Service Controls Guide.
Logging and detection
App Engine writes Admin Activity audit logs under appengine.googleapis.com, request logs for every HTTP request, and application logs. Route all three to a central sink; see the Google Cloud Audit Logs and Log Sinks Guide.
| Event | Audited method | Alert on |
|---|---|---|
| New or changed version | apps.services.versions.create, apps.services.versions.patch | Deploys by anyone other than the CI service account, or a change of runtime service account |
| Firewall change | apps.firewall.ingressRules.create, .patch, .batchUpdate, .delete | Any change to the default rule or a broad allow range |
| Application settings | apps.patch | Changes to serving status or application-level settings |
| IAM on runtime account | SetIamPolicy on the service account (IAM service) | New Service Account User or Token Creator grants |
| Service account key creation | CreateServiceAccountKey (IAM service) | Any new key for an App Engine runtime account |
In request logs, watch for traffic to non-serving versions by URL, spikes of 403s from the firewall (blocked traffic is not billed, so the rule is doing its job), and unusual user agents on admin paths. Map detections to MITRE ATT&CK T1078.004 Cloud Accounts for misuse of the runtime identity. Web Security Scanner, run by a project owner from the console, crawls the app for common web vulnerabilities and is a useful periodic check for legacy apps.
Metrics that show it is working
- Share of serving versions running as user-managed service accounts (target: all).
- Default App Engine service accounts still holding Editor (target: zero).
- Principals with project-level Service Account User (target: zero).
- Internal apps with IAP on and a restrictive firewall default (target: all).
- Serving versions on runtimes past end of support, with a dated migration plan for each.
- Service account keys for App Engine runtime accounts (target: zero).
Hardening baseline
Apply in this order, testing each change on a non-production version with a version-specific URL before migrating traffic. Rollback is a traffic migration back to the previous version, so keep it deployed until the new one is proven.
- Inventory: list services, serving versions, runtimes, the service account each version uses, firewall rules, ingress settings and IAP status per project.
- Enforce the organisation policy: turn on
iam.automaticIamGrantsForDefaultServiceAccountsso no new default account gets Editor. - Move versions to user-managed service accounts: create an account per app with the roles it needs, deploy a new version with
service_account, test, migrate traffic. - Right-size the default account: once no serving version uses it, remove Editor (check with Policy Simulator first), and then disable it if nothing else depends on it.
- Tighten deployers: give CI Deployer plus Service Account User on the one runtime account; remove project-level actAs and basic roles.
- Close the front door: add explicit firewall allow rules, set the default to
denywhere the audience is known, restrict ingress, and turn on IAP for internal apps. - Move secrets: move credentials from
app.yamland source into Secret Manager. - Plan runtime migrations: map every version to the runtime support schedule (below) and schedule moves well before deprecation.
- Log and alert: route logs centrally and alert on the events above.
Runtime lifecycle and legacy apps
Google publishes a support schedule for each runtime and defines three stages. At end of support, Google stops applying security updates to runtime components, apps keep serving traffic, and new deploys and updates on that runtime are blocked (an organisation policy can re-enable deploys in some cases); in-app notifications start 90 days before. At deprecation, existing workloads still run, but creation, redeployment and configuration updates are blocked and the re-enable option is removed. At decommissioning, apps still using the runtime may be disabled without further notice.
| Runtime (examples) | End of support | Status in the schedule (checked 8 Nov 2026) |
|---|---|---|
| Python 2.7, Java 8, Go 1.11, PHP 5.5 (legacy) | January 2024 | Deprecated 31 January 2026; decommissioning scheduled 31 January 2027 |
| Python 3.10 | 4 October 2026 | Past end of support |
| Node.js 20 | 30 April 2026 | Past end of support |
| PHP 8.2 | 31 December 2026 | Upcoming |
| Node.js 22 | 30 April 2027 | Upcoming |
| Python 3.11 | 24 October 2027 | Upcoming |
Google notes that key dates may move, so confirm against the live schedule. Legacy apps that rely on bundled services usually need code changes as well as a runtime bump; Google publishes migration guides per legacy runtime. A move is also a good moment to reconsider the platform: many teams move such workloads to Cloud Run while they are changing code anyway. Track unsupported components under NIST SP 800-53 SA-22.
Framework mapping
| Control area | App Engine setting | NIST SP 800-53 Rev 5 |
|---|---|---|
| Least privilege | User-managed runtime accounts, Deployer role, scoped actAs | AC-6, AC-2 |
| Boundary protection | Firewall rules, ingress settings | SC-7 |
| Access enforcement | Identity-Aware Proxy | AC-3 |
| Authenticator management | Secret Manager, no key files | IA-5 |
| Unsupported components | Runtime lifecycle planning | SA-22 |
| Transport protection | HTTPS, managed certificates, secure: always | SC-8 |
| Audit | Admin Activity and request logs | AU-2, AU-6 |
Common misconfigurations
- Every version on the default service account: a bug in any handler then acts with the default account's project roles. Assign user-managed accounts per version.
- Project-level Service Account User for the CI pipeline: the pipeline can then deploy as any account in the project. Grant it on the runtime account only.
- Firewall default left at allow for internal tools: an internal app is then reachable from anywhere. Add allow rules for known ranges and set the default to deny, plus IAP.
- Secrets in app.yaml: anyone with Viewer-type access to the configuration can read them. Use Secret Manager.
- Old versions left serving on version URLs: a non-default version can still be reached directly. Delete versions that are not needed and keep only a rollback candidate.
- Waiting for the runtime deadline: once a runtime is deprecated, even a configuration fix cannot be deployed. Migrate before deprecation.
Practitioner checklist
- Enforce
iam.automaticIamGrantsForDefaultServiceAccountsand remove Editor from App Engine default service accounts. - Run every serving version as a user-managed service account set through
service_account. - Grant deployers App Engine Deployer plus Service Account User on the one runtime account, federated from CI.
- Configure firewall allow rules and a deny default where the audience is known, restrict ingress, and turn on IAP for internal apps.
- Move credentials from
app.yamland source to Secret Manager. - Map every version to the runtime support schedule and migrate legacy runtimes before 31 January 2027.
- Delete unused versions and keep one rollback candidate.
- Alert on version deploys, firewall changes, actAs grants and key creation.
Standards and references
- Google Cloud: App Engine standard security overview
- Google Cloud: Configure App Engine service accounts
- Google Cloud: App Engine roles
- Google Cloud: Creating App Engine firewalls
- Google Cloud: Enabling IAP for App Engine
- Google Cloud: App Engine runtime lifecycle
- Google Cloud: App Engine runtime support schedule
- NIST: SP 800-53 Rev 5 Security and Privacy Controls
Related NHI Mgmt Group resources: Cloud Run and Cloud Functions Security Guide · Identity-Aware Proxy Guide · Google Cloud IAM and Service Accounts Guide · Google Cloud Security Guide