Using Google App Engine Safely

This document lists the security requirements for developing applications in Python or Java on the Google App Engine platform. We include examples that demonstrate how to implement these requirements.

The audience for this document is external vendors developing applications for Google.


Generic Security & Privacy Requirements

In addition to the requirements specific to App Engine, you must also follow the relevant guidelines in the Google Partner Security Documentation. Also note that this document only applies to classic App Engine, as the App Engine Flexible Environment may not be used at this time.

App Engine Security & Privacy Requirements

Administration

App Engine provides three roles with different levels of access to the Administration Console: Viewer, Developer, and Owner. While Owner and Developer are especially powerful because they can modify the application code, even the Viewer role has access to logs (which may contain data such as IP addresses). You must diligently control all three roles.

For applications that process user data, you must limit administrative access to corporate-managed GSuite (formerly Google Apps) accounts; that is, no @gmail accounts.

2-step verification must be enabled for administrative GSuite accounts.

Authentication

Using a non-standard authentication mechanism is more work than reusing an existing library/API, and almost always results in big security blunders. Use OAuth or the Users API to keep your application and your data safe.

Authentication must be done via the Users API (Python, Java) or OAuth (Python, Java).

If you choose to use the Users API (preferred), be careful with the user ID. Exposing raw user IDs could allow someone with malicious intentions to associate a user's activity in one app with activity in another, or obtain their email address.

Do not publicly expose the user ID obtained via Google Accounts API.

Even for administrative access, you must not create or use custom authentication schemes (including ones bundled with application frameworks such as Django).

Do not rely on secret URLs or custom authentication schemes to access non-standard administrative, management, reporting interfaces, or beta versions of the service.

Authorization

Building customized authorization models invites mistakes. Leverage the per-URL basic access control functionality that is built into the Java and Python App Engine SDK application configuration files:

Python

The login parameter for each URL handler controls access to that URL. The login parameter is set in the handlers section in app.yaml and can have one of three possible values:

  • optional—This is the default value, and the user does not need to be signed in.
  • required—Users must be signed in to access the URL and execute the handler.
  • admin—Users must be administrators of the application in addition to being signed in.

In the following example, users must be logged in to access the /profile/ URL, and only administrators can access /admin/.

  handlers:
  - url: /profile/.*
    script: user_profile.py
    login: required

  - url: /admin/.*
    script: admin.py
    login: admin

  - url: /.*
    script: welcome.py

Java

Java web applications use web.xml to define how URLs map to servlets, and which URLs require authentication. The <security-constraint> element defines a security constraint for URLs that match a pattern. To ensure that only logged in users can access a URL, specify a <role-name> of *. Specify a <role-name> of admin to restrict access to administrators.

  <security-constraint>
    <web-resource-collection>
        <url-pattern>profile/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>*</role-name>
    </auth-constraint>
  </security-constraint>

Built-in Handlers

App Engine comes with built-in handlers that provide powerful access to application data and functionality.

_ah_admin and remote_api

The /_ah/admin and remote_api handlers make it possible for an XSS to arbitrarily modify application code and data within the context of the app. By default, these handlers are disabled. Given the risk, they must not be enabled for an app that has access to production data.

The /_ah/admin and remote_api handlers must not be enabled.

Cron

Cron handlers run certain tasks regularly. They are defined in cron.yaml (Python) or cron.xml (Java) files and must be restricted to authenticated admins. Otherwise, arbitrary users could execute the cron handlers. For examples on how to restrict cron handlers, refer to the Authorization section.

Restrict cron handlers to admin users.

Requests from the App Engine environment to cron handlers contain this HTTP header: X-AppEngine-Cron: true

App Engine strips this header from user requests unless the user is an administrator of the application. All cron handlers should check for the presence of this header to mitigate against cross-site request forgery attacks against the application administrators, which would allow an attacker to execute cron tasks.

Cron handlers must verify the presence of the X-AppEngine-Cron HTTP header.

The following code snippet shows how to do this in Java:

  // code snippet inside the get/post handler
  if (request.getHeader("X-AppEngine-Cron") == null) 123
    throw new IllegalStateException("attempt to access cron handler directly, " +
                                    "missing custom App Engine header");
  }

The following code snippet shows how to do this in Python (assumes a framework like WebOb or webapp2):

  # code snippet inside the get/post handler
  header = request.headers.get('X-AppEngine-Cron', None)
  if not header:
    raise ValueError('attempt to access cron handler directly, '
                     'missing custom App Engine header')

Task Handlers

With the Task Queue API, applications can perform work outside of user requests. As with cron handlers, task handlers should be restricted to authenticated admins.

Restrict task handlers to admin users.

For examples on how to restrict task handlers, refer to the Authorization section.

HTTP Headers

The following headers are present in requests initiated by App Engine and stripped from non-administrator-issued user requests:

  • X-AppEngine-QueueName
  • X-AppEngine-TaskName
  • X-AppEngine-TaskRetryCount
  • X-AppEngine-TaskExecutionCount
  • X-AppEngine-TaskETA

If one or more of these headers are present in a task queue request, the request is legitimate.

Task queues must verify the presence of one of the above HTTP headers.

The following code snippet shows how to do this in Java:

  // code snippet inside the get/post handler
  if (request.getHeader("X-AppEngine-QueueName") == null) 123
    throw new IllegalStateException("attempt to access task handler directly, " +
                                    "missing custom App Engine header");
  }

SSL

Your whole application must be served over SSL:

The HTTPS protocol must be enforced for the whole application.

Python

To configure a Python application to accept only SSL connections:

In your app.yaml, for each URL handler, set the secure parameter to always. The following example sets SSL for the youraccount handler.

  handlers:

  - url: /youraccount/.*
    script: accounts.py
    login: required
    secure: always

Java

To configure a Java application to accept only SSL connections:

  1. In appengine-web.xml, set the ssl-enabled tag to true.
      <ssl-enabled>true</ssl-enabled>
    
  2. In web.xml, set the transport-guarantee parameter to CONFIDENTIAL for all URLs. The following example shows how to enforce SSL for all URLs.
      <security-constraint>
        <web-resource-collection>
          <url-pattern>*</url-pattern>
        </web-resource-collection>
        <user-data-constraint>
          <transport-guarantee>CONFIDENTIAL</transport-guarantee>
        </user-data-constraint>
      </security-constraint>
    

URL Fetch

By default, the App Engine URL Fetch APIs do not validate SSL certificates. This makes connections vulnerable to man-in-the-middle attacks. If you use these APIs, you must enable certificate validation.

Enable certificate validation.
Python: Set the validate_certificate parameter of the fetch() method to True.
Java: Use the FetchOptions builder to configure the API.

Storage

Google Cloud Platform offers the following storage solutions:

Storage Description Recommended
Cloud Datastore A schemaless object datastore with automatic caching, a sophisticated query engine, and atomic transactions. YES
Cloud Storage A storage service for objects and files up to terabytes in size, and accessible to App Engine apps via the Google Cloud Storage client library. YES
Cloud SQL A relational SQL database for your App Engine application, based on the familiar MySQL database. CONDITIONAL*

Datastore

The impact of an SQL-injection attack on an application that uses Cloud Datastore is typically less than on applications that use a standard SQL backend. However there is still some risk of leaking data.

Never construct queries by concatenating strings entered by users.

Python

GQL queries to the datastore must use parameter binding to prevent SQL-injection-like attacks. This is described in the NDB documentation and the Datastore documentation. Declare query arguments with a colon followed by a number or named argument:

NDB
  q = ndb.gql("SELECT * FROM Account WHERE spam > :1", 10)
  q = ndb.gql("SELECT * FROM Account WHERE spam > :threshold", threshold=10)
Datastore
  q = GqlQuery("SELECT __key__ FROM Song WHERE composer = :1", "Lennon, John")
  q = GqlQuery("SELECT * FROM Song WHERE composer = :composer", composer="Lennon, John")
Java

JDOQL queries must use parameter substitution to prevent SQL-injection-like attacks. However, constructing raw queries in source code is often error-prone. We recommend using an Object Relational Mapping (ORM) library. Some examples of ORMs are in the Datastore documentation.

  // pm is an instance of javax.jdo.PersistenceManager
  Query q = pm.newQuery(Person.class);
  q.setFilter("lastName == lastNameParam");

  q.setOrdering("height desc");
  q.declareParameters("String lastNameParam");
  q.execute(userControlledParameter);

Key / Secret Storage

Storing sensitive information (e.g. encryption key material) in source code is dangerous as this makes it available to anyone with access to the source code repository. Sensitive key material can be stored in the Cloud Datastore. However, if the loss of key material would prevent data access, the key material must be available for escrow. You should always have a plan for rotating or versioning the encryption key in case of loss or compromise.

Key material (e.g. data encryption keys) should be documented and backed up safely outside the system.


*Cloud SQL conditions: While Cloud SQL is an excellent storage backend, the security industry’s experience is that almost all development teams using SQL introduce SQL injection bugs that result in massive compromise. It is possible to safely use Cloud SQL in a project that enforces: rigorous style standards; code reviews aided by static analysis tools; the exclusive use of APIs that prevent SQL injection by requiring that query parameters be bound to placeholder arguments. With all these measures in place, it may be acceptable to use Cloud SQL. Per the web application security requirements, please have your design vetted to ensure your use case will be approved.