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:
-
In
appengine-web.xml
, set thessl-enabled
tag totrue
.<ssl-enabled>true</ssl-enabled>
-
In
web.xml
, set thetransport-guarantee
parameter toCONFIDENTIAL
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.