Hide
Google Cloud Storage

Access Control

There are three ways to control access to Google Cloud Storage buckets and objects:

  • Access Control Lists (ACLs), which provide a way to specify read or write access for specified Google accounts and groups.
  • Signed URLs (Query String Authentication), which provide a way to give time-limited read or write access to anyone in possession of the URL, regardless of whether they have a Google account,
  • Signed Policy Documents, which provide a way to specify what can be uploaded to a bucket. Policy documents allow greater control over size, content type, and other upload characteristics than signed URLs, and can be used by website owners to allow visitors to upload files to Google Cloud Storage.

These are not mutually exclusive. You can use ACLs to protect buckets and objects, while at the same time using signed URLs or policy documents so that can access those resources, bypassing the ACL mechanism.

This document shows you how to configure access control using the Google Developers Console, gsutil, and the XML and JSON APIs. The Developers Console and gsutil are easiest to use if you are new to access control. If you are specifying ACLs using an API, you should have experience making HTTP requests. You can use your favorite tool or application to send the HTTP requests. In the examples, we use the cURL tool. You can get authorization tokens to use in the cURL examples from the OAuth 2.0 Playground.

For examples of sharing and collaboration scenarios that involve setting bucket and object ACLs, see Sharing and Collaboration.

Contents

About Access Control Lists

Google Cloud Storage uses access control lists (ACLs) to manage bucket and object access. ACLs are the mechanism you use to share objects with other users and allow other users to access your buckets and objects.

An ACL consists of one or more entries, where each entry grants permissions to a scope. Permissions define the actions that can be performed against a bucket or object (for example, read or write); the scope defines who the permission applies to (for example, a specific user or group of users). Scopes are sometimes referred to as grantees. The maximum number of ACL entries you can create for a bucket or object is 100. When the ACL scope is a group or domain, it counts as one ACL entry regardless of how many users are in the group or domain.

When a user requests access to a bucket or object, the Google Cloud Storage system reads the bucket or object ACL and determines whether to allow or reject the access request. If the ACL grants the user permission for the requested operation, the request is allowed. If the ACL does not grant the user permission for the requested operation, the request fails and a 403 Forbidden error (Access Denied) is returned.

Permissions and scopes

Permissions

Google Cloud Storage lets you assign the following concentric permissions for your buckets and objects as shown in the following table:

READER WRITER OWNER Default
Objects Lets a user download an object's data. You cannot apply this permission to objects. Gives a user READER access. It also lets a user read and write object metadata, including ACLs. Objects have the predefined project-private ACL applied when they are uploaded. Objects are always owned by the original requester who uploaded the object.
Buckets Lets a user list a bucket's contents.1 Lets a user list, create, overwrite, and delete objects in a bucket. 2 Gives a user READER and WRITER permissions on the bucket. It also lets a user read and write bucket metadata, including ACLs. Buckets have the predefined project-private ACL applied when they are created. Buckets are always owned by the project-owners group.

1 The following bucket metadata properties are not returned with a bucket's resource without OWNER: acl, cors, defaultObjectAcl, lifecycle, logging, owner, and projectNumber.
2 The following bucket metadata properties cannot be changed: acl, cors, defaultObjectAcl, lifecycle, logging, versioning and website.

Note: You cannot grant discrete permissions for reading or writing ACLs or other metadata. To let someone read and write ACLs you must grant them OWNER permission.

In this page, we generally refer to the permissions as READER, WRITER, and OWNER, which are how they are specified in the JSON API and the Google Developers Console. If you are using the XML API, the equivalent permissions are READ, WRITE, and FULL_CONTROL, respectively. And, when you use OAuth 2.0 Authentication to authenticate tools and applications (grant permission to them) to access Google Cloud Storage API on your behalf, access is restricted by OAuth scope devstorage.read_only, devstorage.read_write, and devstorage.full_control. The following table summarizes the permissions terminology you will commonly encounter:

JSON API XML API OAuth2 URL
READER READ https://www.googleapis.com/auth/devstorage.read_only
WRITER WRITE https://www.googleapis.com/auth/devstorage.read_write
OWNER FULL_CONTROL https://www.googleapis.com/auth/devstorage.full_control

Scopes

An ACL consists of one or more entries, where each entry grants permissions to a scope. You can specify an ACL scope using any of the following entities:

  • Google Storage ID

    A Google Storage ID is a string of 64 hexadecimal digits that identifies a specific Google account holder or a specific Google group. It is sometimes referred to as a canonical ID. The following is an example of a Google Storage ID:

    84fac329bceSAMPLE777d5d22b8SAMPLE77d85ac2SAMPLE2dfcf7c4adf34da46

    Project teams are identified by a Google Storage ID. The project editors group and project owners group are also identified using Google Cloud Storage IDs. These IDs are unique to a project.

  • Google account email address

    Every user who has a Google account must have a unique email address associated with that account. You can specify a scope by using any email address that is associated with a Google account, such as a gmail.com address.

    Google Cloud Storage remembers email addresses as they are provided in ACLs until the entries are removed or overwritten. If a user changes email addresses, you should update ACL entries to reflect these changes.

  • Google group email address

    Every Google group has a unique email address that is associated with the group. For example, the Google Cloud Storage Announce has the following email address: gs-announce@googlegroups.com. You can find the email address that is associated with a Google group by clicking About on the homepage of every Google group. For more information about Google groups, see the Google groups homepage.

    Like Google account email addresses, Google Cloud Storage remembers group email addresses as they are provided in ACLs until the entries are removed or overwritten. You do not need to worry about updating Google Group email addresses because Google Group email addresses are permanent and unlikely to change.

  • Google Apps domain

    Google Apps customers can associate their email accounts with an Internet domain name. When you do this, each email account takes the form username@yourdomain.com. You can specify a scope by using any Internet domain name that is associated with a Google Apps account.

  • Special identifier for all Google account holders

    This special scope identifier represents anyone who is authenticated with a Google account. The special scope identifier for all Google account holders is allAuthenticatedUsers.

  • Special identifier for all users

    This special scope identifier represents anyone who is on the Internet, with or without a Google account. The special scope identifier for all users is allUsers.

Usually, you specify scopes using an email address, a domain, or one of the special identifiers. If you want to specify a scope using a Google Storage ID, you can find the Google Storage ID as shown in the following section.

Finding Google Cloud Storage IDs

You can find a user's Google Storage ID by retrieving the ACL of an object that the user uploaded. An authenticated user must have OWNER permission to read object metadata, which includes the object ACL.

To find a user's Google Storage ID:

Google Developers Console

  1. In the Google Developers Console, find an object that the user uploaded.
  2. Click more actions More actions icon. at the end of the object row and select Edit permissions .
  3. In the permissions dialog, find the permission granted to the user, which includes the user's Google Storage ID.

gsutil

  1. Identify an object that the user uploaded.
  2. Use the gsutil acl command:
    gsutil acl get gs://<path-to-object>
  3. In the response, find the entityId, which is the user's Google Storage ID.

JSON API

  1. Identify an object that the user uploaded.
  2. Retrieve that object's metadata with a get request.
    curl -X GET -H "Authorization: Bearer <token>" \
         https://www.googleapis.com/storage/v1/b/<bucket-name>/o/<object-name>
  3. In the response, find the entityID property.

XML API

  1. Identify an object that the user uploaded.
  2. Retrieve that object's metadata with a GET Object request.
    curl -X GET -H "Authorization: Bearer <token>" \
         http://storage.googleapis.com/<bucket-name>/<object-name>?acl
  3. In the response body, find the <ID> element in the <OWNER> container.

You can also retrieve Google Cloud Storage IDs for project roles (team, editors, and owners) by either retrieving ACLs on a bucket if the ACLs have not been changed from the default, or by using getting them from the Project dashboard.

To find Google Cloud Storage IDs for project groups:

  1. Go to the Google Developers Console.
  2. Select a project by clicking its name.
  3. In the left sidebar, select Storage > Cloud Storage > Storage access.
  4. The project IDs are in the Google Cloud Storage IDs section.

Back to top

Bucket and object ACLs

ACL syntax

The tool or API you use to set and get ACLs determines the ACL syntax you will use. The ACL syntaxes look different, but they contain the same ACL information: entries that grant permissions to scopes.

Google Developers Console

When specifying an ACL in the Google Developers Console, you specify:

  • The ACL entry type: Domain, Group, User, or Project.
  • The ACL entry entity value.
    • For Domain specify a domain, for example "example.com".
    • For Group specify the group's email address.
    • For User specify the user's email address.
    • For Project either specify the Google Storage ID of the project role, Team, Editors, or Owners. See Finding Google Storage IDs.
  • The ACL entry permission: READER, WRITER (bucket only), or OWNER.

The following example shows permissions for a bucket in the Developers Console.

Specifying ACL in console.

You can grant the allUsers or allAuthenticatedUsers scope access to a bucket or object using an entry type User.

gsutil

gsutil acl enables you to specify ACLs in a couple of ways. You can specify:

  • Individual grants. For example, gsutil acl set -u jane@gmail.com:R gs://bucket.
  • Canned ACLs. For example, gsutil acl set private gs://bucket.
  • ACLs in JSON format. For example, gsutil acl set acl.json gs://bucket where acl.json contains ACLs specified in JSON format.

When gsutil returns ACLs for buckets and objects (gsutil acl get), they are in the same JSON format that you can use to set ACLs. ACLs in JSON format use the JSON API property names, such as entity and role. See the JSON API syntax for more information about how to interpret the output or run gsutil help acls.

The following command returns a bucket's ACL.

gsutil acl get gs://<bucket-name>

The following example shows different bucket ACL entries.

[
  {
    "entity": "project-owners-123412341234",
    "projectTeam": {
      "projectNumber": "123412341234",
      "team": "owners"
    },
    "role": "OWNER"
  },
  {
    "entity": "project-editors-123412341234",
    "projectTeam": {
      "projectNumber": "123412341234",
      "team": "editors"
    },
    "role": "OWNER"
  },
  {
    "entity": "project-viewers-123412341234",
    "projectTeam": {
      "projectNumber": "123412341234",
      "team": "viewers"
    },
    "role": "READER"
  },
  {
    "email": "gs-announce@googlegroups.com",
    "entity": "group-gs-announce@googlegroups.com",
    "role": "READER"
  },
  {
    "email": "jane@gmail.com",
    "entity": "user-jane@gmail.com",
    "role": "READER"
  },
  {
    "entity": "allUsers",
    "role": "READER"
  },
  {
    "entity": "allAuthenticatedUsers",
    "role": "READER"
  }
]

JSON API

In the JSON API, ACLs are in JSON format. You must attach JSON to the body of requests to change bucket and object ACLs. JSON is returned when you get bucket and object ACLs.

For definitions of the bucket and object ACL properties, see the BucketAccessControls and ObjectAccessControls resources, respectively.

The following example shows different bucket ACL entries.

"acl": [
  {

   "kind": "storage#bucketAccessControl",
   "id": "example-bucket/project-owners-123412341234",
   "selfLink": "https://www.googleapis.com/storage/v1/b/example-bucket/acl/project-owners-123412341234",
   "bucket": "example-bucket",
   "entity": "project-owners-123412341234",
   "role": "OWNER",
   "projectTeam": {
    "projectNumber": "123412341234",
    "team": "owners"
   },
   "etag": "CDk="
  },
  {

   "kind": "storage#bucketAccessControl",
   "id": "example-bucket/project-editors-123412341234",
   "selfLink": "https://www.googleapis.com/storage/v1/b/example-bucket/acl/project-editors-123412341234",
   "bucket": "example-bucket",
   "entity": "project-editors-123412341234",
   "role": "OWNER",
   "projectTeam": {
    "projectNumber": "123412341234",
    "team": "editors"
   },
   "etag": "CDk="
  },
  {

   "kind": "storage#bucketAccessControl",
   "id": "example-bucket/project-viewers-123412341234",
   "selfLink": "https://www.googleapis.com/storage/v1/b/example-bucket/acl/project-viewers-123412341234",
   "bucket": "example-bucket",
   "entity": "project-viewers-123412341234",
   "role": "READER",
   "projectTeam": {
    "projectNumber": "123412341234",
    "team": "viewers"
   },
   "etag": "CDk="
  },
  {

   "kind": "storage#bucketAccessControl",
   "id": "example-bucket/group-gs-announce@googlegroups.com",
   "selfLink": "https://www.googleapis.com/storage/v1/b/example-bucket/acl/group-gs-announce@googlegroups.com",
   "bucket": "example-bucket",
   "entity": "group-gs-announce@googlegroups.com",
   "role": "READER",
   "email": "gs-announce@googlegroups.com",
   "etag": "CDk="
  },
  {

   "kind": "storage#bucketAccessControl",
   "id": "example-bucket/user-jane@gmail.com",
   "selfLink": "https://www.googleapis.com/storage/v1/b/example-bucket/acl/user-jane@gmail.com",
   "bucket": "example-bucket",
   "entity": "user-jane@gmail.com",
   "role": "READER",
   "email": "jane@gmail.com",
   "etag": "CDk="
  },
  {

   "kind": "storage#bucketAccessControl",
   "id": "example-bucket/allUsers",
   "selfLink": "https://www.googleapis.com/storage/v1/b/example-bucket/acl/allUsers",
   "bucket": "example-bucket",
   "entity": "allUsers",
   "role": "READER",
   "etag": "CDk="
  },
  {

   "kind": "storage#bucketAccessControl",
   "id": "example-bucket/allAuthenticatedUsers",
   "selfLink": "https://www.googleapis.com/storage/v1/b/example-bucket/acl/allAuthenticatedUsers",
   "bucket": "example-bucket",
   "entity": "allAuthenticatedUsers",
   "role": "READER",
   "etag": "CDk="
  }
]

XML API

In the XML API, you work with ACLs in XML format. You must attach an XML document to the body of requests to change bucket and object ACLs. An XML document is returned when you get bucket and object ACLs. The XML document contains the individual bucket or object ACL entries.

The following table provides an overview of the XML API ACL syntax. The RELAX NG Compact Syntax Format Schema describes the exact formatting requirements of the Google ACL XML.

Element Description
AccessControlList Container for Entries and Owner elements.
Owner Container for DisplayName and ID elements. This element is not required for objects since an object is always owned by the user who uploaded it. This element is used when you are using Amazon S3 ACL syntax in a migration scenario.

Amazon S3 is a trademark of Amazon.com, Inc. or its affiliates in the United States and/or other countries.
Entries Container for zero or more Entry elements.
Entry Container for Scope and Permission elements. An Entry must contain only one Scope and one Permission element.
Scope Container for an ID element that defines the ACL scope. Attribute type can be UserByID, UserByEmail, GroupByID, GroupByEmail, GroupByDomain, AllUsers, or AllAuthenticatedUsers.
ID An identifier for the grantee when the permission entry is specified by ID.
EmailAddress The email identifier for the grantee when the permission entry is specified by email.
Domain The domain identifier for the grantee when the permission entry is specified by domain.
Permission The permission granted READ, WRITE, or FULL_CONTROL.
Name Optional element that can be specified or that may be automatically added if the scope is UserByEmail or GroupByEmail.

Some things to note when working with ACLs using the XML API:

  • Only Google ACL XML is accepted.
  • Duplicate scopes are not allowed.

    You can have many entries in your ACL XML, but you cannot have entries with duplicate scopes. For example, you cannot have two entries with the same scope element of jane@example.com.

The following example shows different bucket ACL entries.

<?xml version="1.0" encoding="UTF-8"?>
<AccessControlList>
  <Owner>
    <ID>00b4903a9721...</ID>
  </Owner>
  <Entries>
    <Entry>
      <Scope type="GroupById">
        <ID>00b4903a9722...</ID>
      </Scope>
      <Permission>FULL_CONTROL</Permission>
    </Entry>
    <Entry>
      <Scope type="GroupById">
        <ID>00b4903a9723...</ID>
      </Scope>
      <Permission>FULL_CONTROL</Permission>
    </Entry>
    <Entry>
      <Scope type="GroupById">
        <ID>00b4903a9724...</ID>
      </Scope>
      <Permission>READ</Permission>
    </Entry>
    <Entry>
      <Scope type="GroupByDomain">
        <Domain>example.com</Domain>
      </Scope>
      <Permission>READ</Permission>
    </Entry>
    <Entry>
      <Scope type="GroupByEmail">
        <EmailAddress>gs-announce@googlegroups.com</EmailAddress>
      </Scope>
      <Permission>READ</Permission>
    </Entry>
    <Entry>
      <Scope type="UserByEmail">
        <EmailAddress>jane@gmail.com</EmailAddress>
        <Name>jane</Name>
      </Scope>
      <Permission>READ</Permission>
    </Entry>
    <Entry>
      <Scope type="AllUsers"/>
      <Permission>READ</Permission>
    </Entry>
    <Entry>
      <Scope type="AllAuthenticatedUsers"/>
      <Permission>READ</Permission>
    </Entry>
  </Entries>
</AccessControlList>

The Name element in ACL XML

When you retrieve an ACL from a bucket or object, you might notice an additional <Name> element appended to some of your entries. For example, you might see an entry that looks like the following:

 <Entry>
    <Scope type="UserByEmail">
      <EmailAddress>jane@gmail.com</EmailAddress>
      <Name>Jane</Name>
    </Scope>
    <Permission>FULL_CONTROL</Permission>
 </Entry>

These optional <Name> elements are populated in one of two ways:

  1. It was provided with the bucket or object's ACLs

    When you set ACLs, you may choose to include the <Name> element with your ACL entries. You can provide any value in the <Name> element and Google Cloud Storage remembers these values until the ACL is removed or overwritten. This can be useful if you are using identifiers that aren't easily identifiable, like Google Cloud Storage IDs.

  2. A UserByEmail or GroupByEmail scope was used with a public Google profile

    If you used either of these scopes but did not provide a <Name> element, Google Cloud Storage checks if the user or Google Group of the email address has a public Google profile with a public name. If so, Google Cloud Storage populates the <Name> element automatically with the public name.

How to specify ACLs

The following list is an overview of how you can specify ACLs on buckets and objects. The list does not cover how ACLs are specified when you are using a client library to access Google Cloud Storage, however, the information below can be used for guidance.

Specifying ACLs on buckets

Google Developers Console

New buckets are added as project private. Edit the bucket permissions to change the bucket ACL.

gsutil

New buckets are added as project private. Use gsutil acl set or gsutil acl ch to change the ACL on an existing bucket. For more information, run gsutil help acls.

JSON API

Specify the acl[] property in an insert request (create). If no ACL is specified in the bucket create request, project private is used. For an existing bucket, specify the acl[] property in a patch or update request.

XML API

New buckets are added as project private. After creating a bucket with a PUT Bucket request, you can use a second PUT Bucket request with the ?acl parameter to change bucket ACL.
Specifying ACLs on objects

Google Developers Console

An uploaded object gets the bucket default object ACL. The uploader is added as owner to the ACL. Edit the object permissions to change the object ACL.

gsutil

New objects are added with the bucket default object ACL. When copying in the cloud, you can override this behavior with the -p option. You can change the ACL list of an existing object with gsutil acl set or gsutil acl ch.

JSON API

Specify the acl[] property in the request body or the predefinedAcl query parameter in an insert request. If no ACL is specified in the object create request, the object gets the bucket default object ACL. Edit an existing object ACL using the acl[] property or the predefinedAcl query parameter in a patch or update request.

XML API

New objects are added with the bucket default object ACL. After uploading the object with a PUT Object request, you can change ACL with another PUT request using the ?acl parameter or the x-googl-acl request header.
Specifying default object ACLs

Every bucket has a default object ACL that is applied to all objects uploaded to that bucket that do not have a predefined ACL or ACL (JSON API only) specified in the request. Default object ACLs are discussed in the section Default object ACLs.

Specifying predefined ACLs

Predefined (canned) ACLs can also be used to change bucket and object ACLs. A predefined ACL is an alias for a set of ACL entries that can be used to quickly configure access to resources. For more information, see Applying a predefined ACL.

ACL examples

The following examples change the ACL of an existing object.

Google Developers Console

In the Google Developers Console, find the object and edit its permissions.

The following example shows granting jane@gmail.com OWNER permission and the members of the gs-announce group READER permission on the object paris.jpg.

Setting ACL on object paris.jpg.

gsutil

The following gsutil acl set command uses ACLs from the file acls.txt and applies them to an object named paris.jpg.

gsutil acl set acl.txt gs://travel-maps/paris.jpg

acl.txt is shown below and grants jane@gmail.com OWNER permission and the members of the gs-announce group READER permission on the object paris.jpg.

[
  {
    "entity": "user-84fac329bceSAMPLE777d5d22b8SAMPLE77d85ac2SAMPLE2dfcf7c4adf34da46",
    "entityId": "84fac329bceSAMPLE777d5d22b8SAMPLE77d85ac2SAMPLE2dfcf7c4adf34da46",
    "role": "OWNER"
  },
  {
   "entity": "user-jane@gmail.com",
   "email": "jane@gmail.com",
   "role": "OWNER"
  },
  {
   "entity": "group-gs-announce@googlegroups.com",
   "email": "gs-announce@googlegroups.com",
   "role": "READER"
  }
]

You can also set the same ACL for this object with individual grants. For example, to grant jane@gmail.com READER access you can use gsutil acl ch -u jane@gmail.com:READ gs://travel-maps/paris.jpg.

JSON API

The following cURL command applies a JSON payload from the document acls.json to an object named paris.jpg:

curl -X PATCH --data @acls.json -H "Content-Type: application/json" \
     -H "Authorization: Bearer <token>" \
     https://www.googleapis.com/storage/v1/b/travel-maps/o/paris.jpg

This is a patch request with a JSON body (in this case, acls.json). The request changes the ACL of an object named paris.jpg that is in a bucket named travel-maps. The ACL grants jane@gmail.com OWNER permission and it grants the members of the gs-announce group READER permission on the object paris.jpg. The details of the request are:

PATCH /storage/v1/b/travel-maps/o/paris.jpg HTTP/1.1
Host: www.googleapis.com
Content-Type: application/json
Authorization: Bearer <token>
Content-Length: 597
Date: Wed, 08 Oct 2014 22:37:58 GMT

{
"acl": [
  {
   "entity": "user-84fac329bceSAMPLE777d5d22b8SAMPLE77d85ac2SAMPLE2dfcf7c4adf34da46",
   "role": "OWNER",
   "entityId": "84fac329bceSAMPLE777d5d22b8SAMPLE77d85ac2SAMPLE2dfcf7c4adf34da46"
  },
  {
   "entity": "user-jane@gmail.com",
   "role": "OWNER",
   "email": "jane@gmail.com"
  },
  {
   "entity": "group-gs-announce@googlegroups.com",
   "role": "READER",
   "email": "gs-announce@googlegroups.com"
  }
 ]
}

XML API

The following cURL command applies an XML payload from the document acls.xml to an object named paris.jpg:

curl -X PUT --data-binary @acls.xml -H "Authorization: Bearer <token>" \
     https://storage.googleapis.com/travel-maps/paris.jpg?acl

This is a PUT Object request using the acl query string parameter and the corresponding XML document (in this case, acls.txt). The request changes the ACL of an object named paris.jpg that is in a bucket named travel-maps. The ACL grants jane@gmail.com FULL_CONTROL permission and it grants the members of the gs-announce group READ permission on the object paris.jpg. The details of the request are:

PUT /paris.jpg?acl HTTP/1.1
Host: travel-maps.storage.googleapis.com
Date: Sat, 20 Feb 2010 08:31:08 GMT
Content-Length: 589
Content-Type=application/xml
Authorization: Bearer <token>

<?xml version='1.0' encoding='utf-8'?>
<AccessControlList>
  <Owner>
    <ID>84fac329bceSAMPLE777d5d22b8SAMPLE77d85ac2SAMPLE2dfcf7c4adf34da46</ID>
  </Owner>
  <Entries>
  <Entry>
    <Permission>FULL_CONTROL</Permission>
    <Scope type="UserById">
      <ID>84fac329bceSAMPLE777d5d22b8SAMPLE77d85ac2SAMPLE2dfcf7c4adf34da46</ID>
    </Scope>
  </Entry>
  <Entry>
    <Scope type="UserByEmail">
      <EmailAddress>jane@gmail.com</EmailAddress>
      <Name>Jane</Name>
    </Scope>
    <Permission>FULL_CONTROL</Permission>
  </Entry>
  <Entry>
    <Scope type="GroupByEmail">
      <EmailAddress>gs-announce@googlegroups.com</EmailAddress>
    </Scope>
    <Permission>READ</Permission>
  </Entry>
  </Entries>
</AccessControlList>

The following examples show how to get the ACL of an existing object.

Google Developers Console

In the Google Developers Console, find the object and edit its permissions. See the image above for an example of object permissions.

gsutil

Use the gsutil acl get to return an object's ACL. To return the ACL of the object paris.jpg, you can use:

gsutil acl get gs://travel-maps/paris.jpg

Example response:

[
  {
    "entity": "user-84fac329bceSAMPLE777d5d22b8SAMPLE77d85ac2SAMPLE2dfcf7c4adf34da46",
    "entityId": "84fac329bceSAMPLE777d5d22b8SAMPLE77d85ac2SAMPLE2dfcf7c4adf34da46",
    "role": "OWNER"
  },
  {
    "email": "jane@gmail.com",
    "entity": "user-jane@gmail.com",
    "role": "OWNER"
  },
  {
    "email": "gs-announce@googlegroups.com",
    "entity": "group-gs-announce@googlegroups.com",
    "role": "READER"
  }
]

JSON API

You can retrieve an object's ACL with a get request. The object ACL is returned in JSON format, attached to the body of the response. You must have OWNER permission to apply or retrieve ACLs on a bucket or object.

To return the ACL for the object paris.jpg, you can use

curl -X GET -H "Authorization: Bearer <token>" \
     https://www.googleapis.com/storage/v1/b/travel-maps/o/paris.jpg?projection=full

Example response:

{
 "kind": "storage#object",
 "id": "travel-maps/paris.jpg/1412805837131000",
 "selfLink": "https://www.googleapis.com/storage/v1/b/travel-maps/o/paris.jpg",
 "name": "paris.jpg",
 "bucket": "travel-maps",
 ...
 "acl": [
  {
   ...
   "entity": "user-84fac329bceSAMPLE777d5d22b8SAMPLE77d85ac2SAMPLE2dfcf7c4adf34da46",
   "role": "OWNER",
   "entityId": "84fac329bceSAMPLE777d5d22b8SAMPLE77d85ac2SAMPLE2dfcf7c4adf34da46",
   ...
  },
  {
   ...
   "entity": "user-jane@gmail.com",
   "role": "OWNER",
   "email": "jane@gmail.com",
  },
  {
   ...
   "entity": "group-gs-announce@googlegroups.com",
   "role": "READER",
   "email": "gs-announce@googlegroups.com",
   ...
  }
 ],
 "owner": {
  "entity": "user-84fac329bceSAMPLE777d5d22b8SAMPLE77d85ac2SAMPLE2dfcf7c4adf34da46",
  "entityId": "84fac329bceSAMPLE777d5d22b8SAMPLE77d85ac2SAMPLE2dfcf7c4adf34da46"
 },
 ...
}

You can also use the objectAccessControls resource get method to return individual entries in an object's ACL.

XML API

You can retrieve an object's ACL by using the acl query string parameter in a GET Object request. The ACLs are described in XML, attached to the body of the response. You must have FULL_CONTROL permission to apply or retrieve ACLs on a bucket or object.

To return the ACL for the object paris.jpg, you can use:

curl -X GET -H "Authorization: Bearer <token>" \
     https://storage.googleapis.com/travel-maps/paris.jpg?acl

Example response:

<?xml version="1.0" encoding="UTF-8"?>
<AccessControlList>
  <Owner>
    <ID>84fac329bceSAMPLE777d5d22b8SAMPLE77d85ac2SAMPLE2dfcf7c4adf34da46</ID>
    <Name>Owner Name</Name>
  </Owner>
  <Entries>
    <Entry>
      <Scope type="UserById">
        <ID>84fac329bceSAMPLE777d5d22b8SAMPLE77d85ac2SAMPLE2dfcf7c4adf34da46</ID>
        <Name>Name</Name>
      </Scope>
      <Permission>FULL_CONTROL</Permission>
    </Entry>
    <Entry>
      <Scope type="UserByEmail">
        <EmailAddress>jane@gmail.com</EmailAddress>
        <Name>Jane</Name>
      </Scope>
      <Permission>FULL_CONTROL</Permission>
    </Entry>
    <Entry>
      <Scope type="GroupByEmail">
        <EmailAddress>gs-announce@googlegroups.com</EmailAddress>
      </Scope>
      <Permission>READ</Permission>
    </Entry>
  </Entries>
</AccessControlList>

You can also use the get method of the ObjectAccessControls resource to return a specific ACL entry.

Concentric permissions

When specifying ACLs in Google Cloud Storage, you do not need to list multiple scopes to grant multiple permissions. Google Cloud Storage uses concentric permissions so when you grant WRITER permission, you also grant READER permission, and if you grant OWNER permission, you also grant READER and WRITER permission.

When specifying an ACL using the Google Developers Console, JSON API, or gsutil, you can specify multiple scopes for the same entry. The most permissive permission is the access granted to the scope. For example, if you provide two entries for a user, one with READER permission and one with WRITER permission on a bucket, the user will have WRITER permission on the bucket.

In the XML API, it is not possible to provide two ACL entries with the same scope. For example, granting a user READ permission and WRITE permission on a bucket will result in an error. Instead, grant the user WRITE permission which also grants the user READ permission.

Applying a predefined ACL

A predefined or canned ACL is an alias for a set of specific ACL entries that you can use to quickly apply many ACL entries at once to a bucket or object. Predefined ACLs are defined for common scenarios such as revoking all access permissions except for owner permission (predefined ACL private), or making an object publicly readable (predefined ACL public-read).

The table below lists predefined ACLs that you can use and shows which ACL entries are applied for each predefined ACL. When using the table below, note that:

  • For reference, bucket owner is the project owners group, and object owner is the user that created the object. If an object was created by an anonymous user, then the object owner is the project owners group.
  • In the table, the JSON API descriptions of permissions, OWNER, WRITER, and READER, are used. The equivalent XML API scopes are FULL_CONTROL, WRITE, and READ.
  • Predefined ACLs used with gsutil have the same names as those used in XML API. The JSON API names for predefined ACLs are different and are noted in parentheses.
Predefined ACL Description
project-private
(projectPrivate)
Gives permission to the project team based on their roles. Anyone who is part of the team has READER permission and project owners and project editors have OWNER permission. This is the default ACL for newly created buckets. This is also the default ACL for newly created objects unless the default object ACL for that bucket has been changed.
private Gives the bucket or object owner OWNER permission for a bucket or object and removes all other access permissions.
public-read
(publicRead)
Gives the bucket or object owner OWNER permission and gives all users, both authenticated and anonymous, READER permission. When you apply this to an object, anyone on the Internet can read the object without authenticating. When you apply this to a bucket, anyone on the Internet can list objects without authenticating.

Important: By default, publicly readable objects are served with a Cache-Control header allowing such objects to be cached for 3600 seconds. If you need to ensure that updates become visible immediately, you should set a Cache-Control header of "Cache-Control:private, max-age=0, no-transform" on such objects. For help doing this, see the gsutil setmeta command.

Important: Setting a bucket or object to public-read will remove all OWNER and WRITER permissions from everyone except the bucket or object owner.
public-read-write
(publicReadWrite)
Gives the bucket owner OWNER permission and gives all users, both authenticated and anonymous, READER and WRITER permission. This ACL applies only to buckets. When you apply this to a bucket, anyone on the Internet can list, create, overwrite and delete objects without authenticating.

Important: Setting a bucket to public-read-write will allow anyone on the Internet to upload anything to your bucket. You will be responsible for this content.
authenticated-read
(authenticatedRead)
Gives the bucket or object owner OWNER permission and gives all authenticated Google account holders READER permission. All other permissions will be removed.
bucket-owner-read
(bucketOwnerRead)
Gives the object owner OWNER permission and gives the bucket owner READER permission. All other permissions will be removed. This is used only with objects.
bucket-owner-full-control
(bucketOwnerFullControl)
Gives the object and bucket owners OWNER permission. All other permissions will be removed. This is used only with objects.

The following examples show how to apply a predefined ACL to an object during object upload.

gsutil

To apply the predefined ACL bucket-owner-read while uploading an object paris.jpg to a bucket travel-maps, use the -a option with the gsutil cp command.

gsutil cp -a bucket-owner-read paris.jpg gs://travel-maps

JSON API

To apply the predefined ACL bucketOwnerRead while uploading an object paris.jpg to a bucket travel-maps, you can use the predefinedAcl query string parameter in an insert request.

curl -X POST --data-binary @paris.jpg -H "Content-Type: image/jpeg" -H "Authorization: Bearer <token>"  \
     "https://www.googleapis.com/upload/storage/v1/b/travel-maps/o?name=paris.jpg&predefinedAcl=bucketOwnerRead"

The details of the request are:

POST /upload/storage/v1/b/travel-naps/o?name=paris.jpg&amppredefinedAcl=bucketOwnerRead HTTP/1.1
Host: www.googleapis.com
Content-Type: image/jpeg
Authorization: Bearer <token>
Content-Length: 12345
Date: Fri, 10 Oct 2014 00:02:38 GMT

XML API

To apply the predefined ACL bucket-owner-read while uploading an object paris.jpg to a bucket travel-maps, you can use the x-goog-acl header in a Put Object request.

curl -X PUT --upload-file paris.jpg -H "x-goog-acl: bucket-owner-read" \
     -H "Authorization: Bearer <token>"  \
     https://storage.googleapis.com/travel-maps/paris.jpg

The details of the request are:

PUT /paris.jpg HTTP/1.1
Host: travel-maps.storage.googleapis.com
Date: Thu, 09 Oct 2014 23:06:08 GMT
Content-Length: 12345
Content-Type: image/jpg
x-goog-acl: bucket-owner-read
Authorization: Bearer <token>

12345 bytes in entity body

You can also apply a predefined ACL to an existing bucket or object. Applying a predefined ACL to an existing bucket or object is useful if you want to change from one predefined ACL to another, or you want to update custom ACLs to a predefined ACL. The following examples show how to apply a predefined ACL to an existing object.

gsutil

To apply the predefined ACL private to the object paris.jpg in the bucket travel-maps, use the gsutil acl set command.

gsutil acl set private gs://travel-maps/paris.jpg

JSON API

To apply the predefined ACL private to the object paris.jpg in the bucket travel-maps, you can use the predefinedAcl query string parameter and specify an empty acl property in a patch request.

curl -X PATCH --data '{"acl": []}'  -H "Content-Type: application/json" \
     -H "Authorization: Bearer <token>"  \
     https://www.googleapis.com/storage/v1/b/travel-maps/o/paris.jpg?predefinedAcl=private

The details of the request are:

PATCH /storage/v1/b/travel-maps/o/paris.jpg?predefinedAcl=private HTTP/1.1
Host: www.googleapis.com
Content-Type: application/json
Authorization: Bearer <token>
Content-Length: 11
Date: Fri, 10 Oct 2014 18:57:59 GMT

XML API

To apply the predefined ACL private to the object paris.jpg in the bucket travel-maps, you can use the x-goog-acl with the acl query string parameter in a Put Object request, but do not include an XML document in your request.

curl -X PUT -H "Content-Length: 0" \
     -H "Authorization: Bearer <token>" -H "x-goog-acl: private" \
     https://storage.googleapis.com/travel-maps/paris.jpg?acl

The details of the request are:

PUT /paris.jpg?acl HTTP/1.1
Host: travel-maps.storage.googleapis.com
Date: Thu, 09 Oct 2014 23:14:59 GMT
Content-Length: 0
x-goog-acl: private
Authorization: Bearer <token>

empty entity body

Predefined ACLs cannot be set using the Google Developers Console.

Back to top

Default bucket and object ACLs

Default bucket ACL

All buckets are owned by the project owners group. Project owners are granted OWNER permission automatically to all buckets inside their project. When you create a project, you are automatically added as a project owner.

If you create a bucket with the default bucket ACL—that is, you do not specify a predefined ACL when you create the bucket—your bucket has the predefined project-private ACL applied to it. The project-private ACL gives additional permissions to project team members based on their roles. These additional permissions are defined as follows:

  • All Project Team Members

    The project-private ACL provides all team members with READER access to buckets in a project. All project team members can list objects within buckets. All project team members can also list buckets within a project, independent of bucket ACLs.

  • Project Editors

    The project-private ACL provides project editors with OWNER permissions to buckets in a project. Project editors can list a bucket's contents, and create, overwrite, or delete objects in a bucket. Project editors can also list, create, and delete buckets, independent of bucket ACLs.

  • Project Owners

    The project-private ACL provides project owners with OWNER permissions. Project owners can also perform all tasks that project editors can perform, in addition to administrative tasks like adding and removing team members, and changing billing information.

Project teams, project editors, and project owners are identified using Google Cloud Storage IDs. You can find these Google Cloud Storage IDs in the Google Developers Console. This can be useful if you want to use these IDs to customize access to objects and buckets. For more information, see Finding Google Cloud Storage IDs.

Default object ACL

By default, anyone who has OWNER permission or WRITER permission on a bucket can upload objects into that bucket. When you upload an object, you can provide a predefined ACL or not specify an ACL at all. If you don't specify an ACL, Google Cloud Storage applies the bucket's default object ACL to the object. Every bucket has a default object ACL and this ACL is applied to all objects uploaded to that bucket without a predefined ACL or an ACL specified in the request (JSON API only). The initial value for the default object ACL of every bucket is project-private. You can change the default object ACL of a bucket, as describe in Changing default object ACLs.

Based on how objects are uploaded, object ACLs are applied accordingly:

  • Authenticated Uploads

    If you make an authenticated request to upload an object and you do not specify any object ACLs when you upload it, you are listed as the owner of the object and the predefined project-private ACL is applied to the object by default. This means:

    • You (the person who uploaded the object) are listed as the object owner. Object ownership cannot be changed by modifying ACLs. You can change object ownership only by overwriting an object.
    • You (the object owner) are granted OWNER permission on the object. If you attempt to give less than OWNER permission to the owner, Google Cloud Storage automatically escalates the permission to OWNER.
    • The project owners and project editors group have OWNER permission on the object.
    • The project team members group has READER permission on the object.

  • Anonymous Uploads

    If an unauthenticated (anonymous) user uploads an object, which is possible if a bucket grants the allUsers group WRITER or OWNER permission, then the default bucket ACLs are applied to the object as described above.

    Anonymous users cannot specify a predefined ACL during object upload. Anonymous users have the following Google Storage ID:

    00b4903a97d9812a16c37f78443e7c994fa3b7e3c8d5cdc1e0f2c75c6f65318d

Changing default object ACLs

You can change the default object ACL for a bucket. For example, you may want to specify that only a certain group of users have access to all objects in a particular bucket but you do not want to manually set these ACLs on every new object. In these examples, you can change the default object ACL for the bucket that holds these objects. All new objects will have the new default object ACL applied to them.

To view and change the default object ACL for a bucket:

Google Developers Console

  1. In the Developers Console, select Storage > Storage browser to see a list of buckets.
  2. Click the more actions More actions icon. next to the bucket and select Edit object default permissions.
  3. In the permissions dialog change existing values or add new values. For help using this dialog box, see ACL Syntax.
  4. Click Save.

gsutil

  1. You can retrieve the default object ACL with a with the gsutil defacl command.
    gsutil defacl get gs://<bucket-name>
  2. Use gsutil defacl ch or gsutil defacl set to modify the default object ACL. For example, the following command adds jane@gmail.com to the default object ACL for a bucket:
    gsutil defacl ch -u jane@gmail.com:READER gs://<bucket-name>

    You can also specify the default object ACL from a file. For more information, see the help for gsutil defacl.

JSON API

  1. You can retrieve the default object ACL with a get request.
    curl -X GET -H "Authorization: Bearer <token>" \
        https://www.googleapis.com/storage/v1/b/<bucket-name>?projection=full
  2. The following patch request replaces the default object ACL with the ACL specified in defacls.json:
    curl -X PATCH --data @defacls.json -H "Content-Type: application/json" -H "Authorization: Bearer <token>" \
        https://www.googleapis.com/storage/v1/b/<bucket-name>

    An example of defacls.txt:

    {
     "defaultObjectAcl": [
      {
       "email": "jane@gmail.com",
       "entity": "user-jane@gmail.com",
       "role": "READER"
      }
     ]
    }

XML API

  1. You can retrieve the default object ACL with a GET Bucket request and the ?defaultObjectAcl parameter.
    curl -X GET -H "Authorization: Bearer <token>" \
        https://storage.googleapis.com/<bucket-name>?defaultObjectAcl
  2. The following PUT Bucket request with the ?defaultObjectAcl parameter replaces the default object ACL with the ACL specified in acls.xml:
    curl -X PUT --data-binary @acls.xml -H "Authorization: Bearer <token>" \
        http://storage.googleapis.com/<bucket-name>?defaultObjectAcl

    An example of acls.txt:

    <AccessControlList>
      <Entries>
      <Entry>
        <Permission>FULL_CONTROL</Permission>
        <Scope type="GroupByEmail">
          <EmailAddress>travel-companions@googlegroups.com</EmailAddress>
        </Scope>
      </Entry>
      </Entries>
    </AccessControlList>

The syntax of ACLs is discussed in ACL syntax. You can also specify a predefined ACL as the default object ACL as well.

To set the default object ACL for a bucket to a predefined ACL:

gsutil

Use the gsutil defacl command with the name of the predefined ACL. For example, set the default object ACL to project-private with:

gsutil defacl set project-private gs://<bucket-name>

XML API

Use a PUT Bucket request with the ?defaultObjectAcl parameter and the x-goog-acl header.

curl -X PUT -H "x-goog-acl: project-private" -H "Content-Length: 0" -H "Authorization: Bearer <token>" \
    http://storage.googleapis.com/<bucket-name>?defaultObjectAcl

To check if a bucket has a modified default object ACL, you can compare a bucket's current default object ACL with the default object ACL for a newly created bucket shown below. The default object ACL for a newly created bucket is equivalent to the predefined project-private ACL.

The default object ACL for a newly created bucket:

Google Developers Console

In the example below, the project ID is "123412341234"; your project ID will be different.

Storage bucket default object ACL.

gsutil

In the example below, the project ID is "123412341234"; your project ID will be different.

[
  {
    "entity": "project-owners-123412341234",
    "projectTeam": {
      "projectNumber": "123412341234",
      "team": "owners"
    },
    "role": "OWNER"
  },
  {
    "entity": "project-editors-123412341234",
    "projectTeam": {
      "projectNumber": "123412341234",
      "team": "editors"
    },
    "role": "OWNER"
  },
  {
    "entity": "project-viewers-123412341234",
    "projectTeam": {
      "projectNumber": "123412341234",
      "team": "viewers"
    },
    "role": "READER"
  }
]

JSON API

In the example below, the project ID is "123412341234"; your project ID will be different.

defaultObjectAcl": [
  {
   "kind": "storage#objectAccessControl",
   "entity": "project-owners-123412341234",
   "role": "OWNER",
   "projectTeam": {
    "projectNumber": "123412341234",
    "team": "owners"
   }
  },
  {
   "kind": "storage#objectAccessControl",
   "entity": "project-editors-123412341234",
   "role": "OWNER",
   "projectTeam": {
    "projectNumber": "123412341234",
    "team": "editors"
   }
  },
  {
   "kind": "storage#objectAccessControl",
   "entity": "project-viewers-123412341234",
   "role": "READER",
   "projectTeam": {
    "projectNumber": "123412341234",
    "team": "viewers"
   }
  }
 ]

XML API

In the example below, the project role IDs start with "00b4903a97..."; your project IDs will be different.

<?xml version='1.0' encoding='UTF-8'?>
<AccessControlList>
  <Entries>
    <Entry>
      <Scope type='GroupById'>
        <ID>00b4903a9721...</ID>
      </Scope>
      <Permission>FULL_CONTROL</Permission>
    </Entry>
    <Entry>
      <Scope type='GroupById'>
        <ID>00b4903a9722...</ID>
      </Scope>
      <Permission>FULL_CONTROL</Permission>
    </Entry>
    <Entry>
      <Scope type='GroupById'>
        <ID>00b4903a9723...</ID>
      </Scope>
      <Permission>READ</Permission>
    </Entry>
  </Entries>
</AccessControlList>

Back to top

Managing access control

ACLs, like any other administrative settings, require active management to be effective. Before you make a bucket or object accessible to other users, be sure you know who you want to share the bucket or object with and be sure you know what roles you want each of those people to play. Over time, changes in project management, usage patterns, and organizational ownership may require you to modify ACL settings on buckets and objects, especially if you manage buckets and objects in a large organization or for a large group of users. As you evaluate and plan your access control settings keep the following best practices in mind:

  • Use the principle of least privilege when granting access to your buckets and objects.

    The principle of least privilege is a security guideline for granting privileges or rights. When you grant access based on the principle of least privilege you grant the minimum privilege that's necessary for a user to accomplish their assigned task. For example, if you want to share a file with someone, grant them READER permission and not OWNER permission.

  • Avoid granting OWNER permission to people you do not know.

    Granting OWNER permission allows a user to change ACLs and take control of data. You should use the OWNER permission only when you want to delegate administrative control over objects and buckets.

  • Be careful how you grant permissions for anonymous users.

    The allUsers and allAuthenticatedUsers scopes should only be used when it is acceptable for anyone on the Internet to read and analyze your data. While these are useful scopes for some applications and scenarios, it is usually not a good idea to grant all users OWNER permission.

  • Avoid setting ACLs that result in inaccessible objects.

    An inaccessible object is an object that cannot be downloaded (read) and can only be deleted. This can happen when the owner of an object leaves a project without granting anyone else OWNER or READER permission on the object. To avoid this problem, you can use the bucket-owner-read or bucket-owner-full- control predefined ACLs when you or anyone else uploads objects to your buckets.

  • Be sure you delegate administrative control of your buckets.

    By default, the project owners group is the only entity that has OWNER permission on a bucket when it is created. You should have at least two members in the project owners group at any given time so that if a team member leaves the group, your buckets can still be managed by the other project owners.

Google Cloud Storage helps you adhere to these best practices by enforcing two ACL modification rules. Specifically, the ACL modification rules help prevent you setting ACLs that make data inaccessible. The ACL modification rules are as follows:

  • You cannot apply an ACL that specifies a different bucket or object owner.

    Bucket and object ownership cannot be changed by modifying ACLs. If you apply a new ACL to a bucket or object, be sure that the bucket or object owner remains unchanged in the new ACL.

  • The bucket or object owner will always have OWNER permission of the bucket or object.

    The owner of a bucket is the project owners group, and the owner of an object is either the user who uploaded the object, or the project owners group if the object was uploaded by an anonymous user.

    When you apply a new ACL to a bucket or object, Google Cloud Storage will respectively add OWNER permission to the bucket or object owner if you omit the grants. It will not grant the project owners group OWNER permission for an object (unless the object was created by an anonymous user), so you must explicitly include it if you so desire.

Although you cannot apply ACLs that change the ownership of a bucket or object, you can change the ownership of objects by overwriting them. Overwriting is in effect a delete operation followed immediately by an upload operation. During an upload operation the person who is requesting the upload becomes the owner of the object. So, you can change the ownership of an object by having the new object owner overwrite the object. To overwrite an object the new object owner must have WRITER or OWNER permission on the bucket in which the object resides. You cannot change bucket ownership under any circumstance.

Back to top

Signed URLs (query string authentication)

In some scenarios, you might not want to require your users to have a Google account in order to access Google Cloud Storage, but you still want to control access using your application-specific logic. The typical way to address this use case is to provide a signed URL to a user, which allows the user access to that resource for a limited time. Anyone who knows the URL can access the resource for a limited time. (You specify the expiration time in the query string to be signed.) Signed URLs can be generated for reading, writing, and deleting resources.

This section shows you how to create a signed URL step-by-step as well as how you can quickly create a signed URL with gsutil.

Creating a signed URL step-by-step

Overview of the required steps

To generate a signed URL, you need to the following:

  1. Construct the string to be signed.
  2. Sign the string using the RSA Algorithm.
  3. Assemble the URL.
  4. Share the URL with authorized users.

Signing by the RSA algorithm is supported for the Google Cloud Storage XML API. You can also sign URLs using HMAC when using the XML API for interoperable access.

Constructing the string to be signed

The string to be signed has the following components:

StringToSign = HTTP_Verb + "\n" +
    Content_MD5 + "\n" +
    Content_Type + "\n" +
    Expiration + "\n" +
    Canonicalized_Extension_Headers +
    Canonicalized_Resource

These components are described in the following table:

String Component Description Example
HTTP_Verb Required. The verb to be used with the signed URL. Signed URLs can be used with GET, HEAD, PUT, and DELETE requests. Although Signed URLs cannot be used with POST, you can use the POST signature parameters described in POST Object to authenticate using web forms. GET
Content_MD5 Optional. The MD5 digest value in base64. If you provide this in the string, the client (usually a browser) must provide this HTTP header with this same value in its request. rmYdCNHKFXam78uCt7xQLw==
Content_Type Optional. If you provide this value the client (browser) must provide this HTTP header set to the same value. text/plain
Expiration Required. This is the timestamp (represented as the number of seconds since the Unix Epoch of 00:00:00 UTC on January 1, 1970) when the signature expires. The server will reject any requests received after this timestamp. 1388534400
Canonicalized_Extension_Headers Optional. If these headers are used, the server will check to make sure that the client provides matching values. For information about how to create canonical headers for signing, see About Canonical Extension Headers. x-goog-acl:public-read\nx-goog-meta-foo:bar,baz\n
Canonicalized_Resource Required. The resource being addressed in the URL. For more details, see About Canonical Resources /bucket/objectname

The signature string for the example values in the above table would look like the following for a GET request. Note that newlines are shown as actual new lines here and in the next two examples.

GET


1388534400
/bucket/objectname

The signature string for the example values in the above table would look like the following for a PUT request.

PUT
rmYdCNHKFXam78uCt7xQLw==
text/plain
1388534400
x-goog-acl:public-read
x-goog-meta-foo:bar,baz
/bucket/objectname

Suppose a resumable upload has been initiated and it has returned a resumable session URI (uploadId), then you can create a string to be signed for a PUT request that will upload object data.

PUT

image/jpeg
1388534400
/bucket/objectname?uploadType=resumable&upload_id=uploadId

After you have constructed the string to sign, you must sign the string, and then assemble the URL using the signature.

About canonical extension headers

You construct the Canonical Extension Headers portion of the message by concatenating all extension (custom) headers that begin with x-goog-. However, you cannot perform a simple concatenation. You must concatenate the headers using the following process:

  1. Make all custom header names lowercase.
  2. Sort all custom headers by header name using a lexicographical sort by code point value.
  3. Eliminate duplicate header names by creating one header name with a comma-separated list of values. Be sure there is no whitespace between the values and be sure that the order of the comma-separated list matches the order that the headers appear in your request. For more information, see RFC 2616 section 4.2.
  4. Replace any folding whitespace or newlines (CRLF or LF) with a single space. For more information about folding whitespace, see RFC 2822 section 2.2.3.
  5. Remove any whitespace around the colon that appears after the header name.
  6. Append a newline (U+000A) to each custom header.
  7. Concatenate all custom headers.

Important: You must use both the header name and the header value when you construct the Canonical Extension Headers portion of the query string. Be sure to remove any whitespace around the colon that separates the header name and value. For example, using the custom header x-goog-acl: private without removing the space after the colon will return a 403 Forbidden because the request signature you calculate will not match the signature Google calculates.

About canonical resources

You construct the Canonicalized_Resource portion of the message by concatenating the resource path (bucket and object and subresource) that the request is acting on. To do this, you can use the following process:

  1. Begin with an empty string.
  2. If the bucket name appears in the Host header, add a slash and the bucket name to the string (for example, /travel-maps). If the bucket name appears in the path portion of the HTTP request, do nothing.
  3. Add the path portion of the HTTP request to the string, excluding any query string parameters. For example, if the path is /europe/france/paris.jpg?cors and you already added the bucket travel-maps to the string, then you need to add /europe/france/paris.jpg to the string.
  4. If the request is scoped to a subresource, such as ?cors, add this subresource to the string, including the question mark.

Be sure to copy the HTTP request path literally: that is, you should include all URL encoding (percent signs) in the string that you create. Also, be sure that you include only query string parameters that designate subresources (such as cors). You should not include query string parameters such as ?prefix, ?max-keys, ?marker, and ?delimiter.

Signing strings

Note: These instructions assume that you have created an OAuth client ID and private keys for a service account type application, as described in the Google Developers Console Help. If you have not already created those, please follow the help to create your client ID and private keys.

You sign the string you constructed using RSA signatures with SHA256 to authenticate requests. You will need these items in order to sign:

How to sign

The following code is a self-contained, running example that you can use to sign a string, once you have the string constructed as described previously, private key, and email form of the client ID. You must have the Apache Commons Codec in order to run this sample.

The example below assumes that the downloaded pkcs12 file has the extension .p12 (for example, key.p12). The code takes three arguments: the path to the key, a password (notasecret), and the data you want to sign.

Important: Each programming language and library has a different way of generating RSA signatures using SHA-256 as the hash function. Google Cloud Storage expects the Base64 encoded signature in its APIs. The GoogleAccessId is the email form of the Client Id.

Java
/*
 * Copyright (c) 2014 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
*/


/*
Demonstrates various Google Cloud Storage Signed URL operations
https://cloud.google.com/storage/docs/access-control#Signed-URLs

Replace SERVICE_ACCOUNT_EMAIL,SERVICE_ACCOUNT_PKCS12_FILE_PATH,BUCKET_NAME incode below
*/

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Signature;

// From Apache Commons Codec.
// https://commons.apache.org/codec/
import org.apache.commons.codec.binary.Base64;


public class GCSSignedURLExample {

    final String SERVICE_ACCOUNT_EMAIL = "<YOUR_SERVICE_ACCOUNT_EMAIL>@developer.gserviceaccount.com";
    final String SERVICE_ACCOUNT_PKCS12_FILE_PATH = "YOUR_SERVICE_ACCOUNT_KEY.p12";
    final long expiration = System.currentTimeMillis()/1000 + 60;

    final String BUCKET_NAME = "YOURBUCKET";
    String OBJECT_NAME = "somerandomfile.txt";

    PrivateKey key;

    public static void main(String[] args) {
        GCSSignedURLExample tc = new GCSSignedURLExample();
    }

    public GCSSignedURLExample() {
        try {

             key = loadKeyFromPkcs12(SERVICE_ACCOUNT_PKCS12_FILE_PATH, "notasecret".toCharArray());

             System.out.println("======= PUT File =========");
             String put_url = this.getSigningURL("PUT");

             URL url = new URL(put_url);
             HttpURLConnection httpCon = (HttpURLConnection) url.openConnection();
             httpCon.setDoOutput(true);
             httpCon.setRequestMethod("PUT");
             OutputStreamWriter out = new OutputStreamWriter(httpCon.getOutputStream());
             out.write("Lorem ipsum");
             out.close();

             System.out.println("PUT Request URL: " + put_url);
             System.out.println("PUT Response code: " + httpCon.getResponseCode());
             renderResponse(httpCon.getInputStream());

             System.out.println("======= GET File =========");

             String get_url = this.getSigningURL("GET");
             url = new URL(get_url);
             httpCon = (HttpURLConnection) url.openConnection();
             httpCon.setRequestMethod("GET");

             System.out.println("GET Request URL " + get_url);
             System.out.println("GET Response code: " + httpCon.getResponseCode());
             renderResponse(httpCon.getInputStream());

             System.out.println("======= DELETE File ======");

             String delete_url = this.getSigningURL("DELETE");
             url = new URL(delete_url);
             httpCon = (HttpURLConnection) url.openConnection();
             httpCon.setRequestMethod("DELETE");

             System.out.println("DELETE Request URL " + get_url);
             System.out.println("DELETE Response code: " + httpCon.getResponseCode());
             renderResponse(httpCon.getInputStream());

        }
        catch (Exception ex) {
            System.out.println("Error : " + ex);
        }
}
    private void renderResponse(InputStream is) throws Exception {
         BufferedReader in = new BufferedReader(new InputStreamReader(is));
         String inputLine;
            while ((inputLine = in.readLine()) != null)
                System.out.println(inputLine);
         in.close();
    }
    private String getSigningURL(String verb) throws Exception {
         String url_signature = this.signString(verb + "\n\n\n" + expiration + "\n" + "/" + BUCKET_NAME + "/" + OBJECT_NAME  );
         String signed_url = "https://storage.googleapis.com/" + BUCKET_NAME + "/" + OBJECT_NAME +
                    "?GoogleAccessId=" + SERVICE_ACCOUNT_EMAIL +
                    "&Expires=" + expiration +
                    "&Signature=" + URLEncoder.encode(url_signature, "UTF-8");
         return signed_url;
    }

    private static PrivateKey loadKeyFromPkcs12(String filename, char[] password) throws Exception {
        FileInputStream fis = new FileInputStream(filename);
        KeyStore ks = KeyStore.getInstance("PKCS12");
        ks.load(fis, password);
        return (PrivateKey) ks.getKey("privatekey", password);
    }

    private String signString(String stringToSign) throws Exception {
        if (key == null)
          throw new Exception("Private Key not initalized");
        Signature signer = Signature.getInstance("SHA256withRSA");
        signer.initSign(key);
        signer.update(stringToSign.getBytes("UTF-8"));
        byte[] rawSignature = signer.sign();
        return new String(Base64.encodeBase64(rawSignature, false), "UTF-8");
    }
}
Python

This Python example can be found at the storage-signed-urls-python repository on GitHub.

# Copyright 2013 Google, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Contains an example of using Google Cloud Storage Signed URLs."""

import base64
import datetime
import md5
import sys
import time

import Crypto.Hash.SHA256 as SHA256
import Crypto.PublicKey.RSA as RSA
import Crypto.Signature.PKCS1_v1_5 as PKCS1_v1_5
import requests

try:
  import conf
except ImportError:
  sys.exit('Configuration module not found. You must create a conf.py file. '
           'See the example in conf.example.py.')

# The Google Cloud Storage API endpoint. You should not need to change this.
GCS_API_ENDPOINT = 'https://storage.googleapis.com'


class CloudStorageURLSigner(object):
  """Contains methods for generating signed URLs for Google Cloud Storage."""

  def __init__(self, key, client_id_email, gcs_api_endpoint, expiration=None,
               session=None):
    """Creates a CloudStorageURLSigner that can be used to access signed URLs.
    Args:
      key: A PyCrypto private key.
      client_id_email: GCS service account email.
      gcs_api_endpoint: Base URL for GCS API.
      expiration: An instance of datetime.datetime containing the time when the
                  signed URL should expire.
      session: A requests.session.Session to use for issuing requests. If not
               supplied, a new session is created.
    """
    self.key = key
    self.client_id_email = client_id_email
    self.gcs_api_endpoint = gcs_api_endpoint

    self.expiration = expiration or (datetime.datetime.now() +
                                     datetime.timedelta(days=1))
    self.expiration = int(time.mktime(self.expiration.timetuple()))

    self.session = session or requests.Session()

  def _Base64Sign(self, plaintext):
    """Signs and returns a base64-encoded SHA256 digest."""
    shahash = SHA256.new(plaintext)
    signer = PKCS1_v1_5.new(self.key)
    signature_bytes = signer.sign(shahash)
    return base64.b64encode(signature_bytes)

  def _MakeSignatureString(self, verb, path, content_md5, content_type):
    """Creates the signature string for signing according to GCS docs."""
    signature_string = ('{verb}\n'
                        '{content_md5}\n'
                        '{content_type}\n'
                        '{expiration}\n'
                        '{resource}')
    return signature_string.format(verb=verb,
                                   content_md5=content_md5,
                                   content_type=content_type,
                                   expiration=self.expiration,
                                   resource=path)

  def _MakeUrl(self, verb, path, content_type='', content_md5=''):
    """Forms and returns the full signed URL to access GCS."""
    base_url = '%s%s' % (self.gcs_api_endpoint, path)
    signature_string = self._MakeSignatureString(verb, path, content_md5,
                                                 content_type)
    signature_signed = self._Base64Sign(signature_string)
    query_params = {'GoogleAccessId': self.client_id_email,
                    'Expires': str(self.expiration),
                    'Signature': signature_signed}
    return base_url, query_params

  def Get(self, path):
    """Performs a GET request.
    Args:
      path: The relative API path to access, e.g. '/bucket/object'.
    Returns:
      An instance of requests.Response containing the HTTP response.
    """
    base_url, query_params = self._MakeUrl('GET', path)
    return self.session.get(base_url, params=query_params)

  def Put(self, path, content_type, data):
    """Performs a PUT request.
    Args:
      path: The relative API path to access, e.g. '/bucket/object'.
      content_type: The content type to assign to the upload.
      data: The file data to upload to the new file.
    Returns:
      An instance of requests.Response containing the HTTP response.
    """
    md5_digest = base64.b64encode(md5.new(data).digest())
    base_url, query_params = self._MakeUrl('PUT', path, content_type,
                                           md5_digest)
    headers = {}
    headers['Content-Type'] = content_type
    headers['Content-Length'] = str(len(data))
    headers['Content-MD5'] = md5_digest
    return self.session.put(base_url, params=query_params, headers=headers,
                            data=data)

  def Delete(self, path):
    """Performs a DELETE request.
    Args:
      path: The relative API path to access, e.g. '/bucket/object'.
    Returns:
      An instance of requests.Response containing the HTTP response.
    """
    base_url, query_params = self._MakeUrl('DELETE', path)
    return self.session.delete(base_url, params=query_params)


def ProcessResponse(r, expected_status=200):
  """Prints request and response information and checks for desired return code.
  Args:
    r: A requests.Response object.
    expected_status: The expected HTTP status code.
  Raises:
    SystemExit if the response code doesn't match expected_status.
  """
  print '--- Request ---'
  print r.request.full_url
  for header, value in r.request.headers.iteritems():
    print '%s: %s' % (header, value)
  print '---------------'
  print '--- Response (Status %s) ---' % r.status_code
  print r.content
  print '-----------------------------'
  print
  if r.status_code != expected_status:
    sys.exit('Exiting due to receiving %d status code when expecting %d.'
             % (r.status_code, expected_status))


def main():
  try:
    keytext = open(conf.PRIVATE_KEY_PATH, 'rb').read()
  except IOError as e:
    sys.exit('Error while reading private key: %s' % e)

  private_key = RSA.importKey(keytext)
  signer = CloudStorageURLSigner(private_key, conf.SERVICE_ACCOUNT_EMAIL,
                                 GCS_API_ENDPOINT)

  file_path = '/%s/%s' % (conf.BUCKET_NAME, conf.OBJECT_NAME)

  print 'Creating file...'
  print '================'
  r = signer.Put(file_path, 'text/plain', 'blah blah')
  ProcessResponse(r)
  print 'Retrieving file...'
  print '=================='
  r = signer.Get(file_path)
  ProcessResponse(r)
  print 'Deleting file...'
  print '================'
  r = signer.Delete(file_path)
  ProcessResponse(r, expected_status=204)
  print 'Done.'

if __name__ == '__main__':
  main()
Go
/*
 * Copyright (c) 2014 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

// Package main contains a program that demonstrates various Google Cloud Storage Signed URL operations.
// More information can be found at
// https://cloud.google.com/storage/docs/access-control#Signed-URLs
// http://godoc.org/google.golang.org/cloud/storage#SignedURL
// Replace googleAccessID,serviceAccountPEMFilename,bucket constants
// Note: default bucketname for App Engine projects is formatted as: <app_id>.appspot.com
// Convert your PKCS12 private key file to PEM:
//
//    openssl pkcs12 -in key.p12 -passin pass:notasecret -out key.pem -nodes
//
package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"time"

	"google.golang.org/cloud/storage"
)

const (
	googleAccessID            = "<serviceAccountEmail>@developer.gserviceaccount.com"
	serviceAccountPEMFilename = "YOUR_SERVICE_ACCOUNT_KEY.pem"
	bucket                    = "YOURBUCKET"
	object                    = "somerandomfile.txt"
)

var (
	expiration = time.Now().Add(time.Second * 60) //expire in 60 seconds
)

func main() {
	data, err := ioutil.ReadFile(serviceAccountPEMFilename)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Println("Putting the object")
	opts := &storage.SignedURLOptions{
		ClientID:   googleAccessID,
		PrivateKey: data,
		Method:     "PUT",
		Expires:    expiration,
	}

	putURL, err := storage.SignedURL(bucket, object, opts)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("PUT URL : %v\n", putURL)

	client := &http.Client{}
	var payload = []byte("Lorem Ipsum")
	req, err := http.NewRequest("PUT", putURL, bytes.NewBuffer(payload))
	res, err := client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	res.Body.Close()
	fmt.Printf("Response Code: %s\n", res.Status)

	fmt.Println("Getting the object")
	opts = &storage.SignedURLOptions{
		ClientID:   googleAccessID,
		PrivateKey: data,
		Method:     "GET",
		Expires:    expiration,
	}

	getURL, err := storage.SignedURL(bucket, object, opts)
	if err != nil {
		log.Fatal(err)
	}

	fmt.Printf("GET URL : %v\n", getURL)

	res, err = http.Get(getURL)
	if err != nil {
		log.Fatal(err)
	}

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		log.Fatal(err)
	}
	res.Body.Close()
	fmt.Printf("Response Code: %s\n", res.Status)
	fmt.Printf("%s\n", body)

	fmt.Println("Deleting the object")
	opts = &storage.SignedURLOptions{
		ClientID:   googleAccessID,
		PrivateKey: data,
		Method:     "DELETE",
		Expires:    expiration,
	}

	deleteURL, err := storage.SignedURL(bucket, object, opts)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Printf("DELETE URL : %v\n", deleteURL)
	req, err = http.NewRequest("DELETE", deleteURL, nil)
	res, err = client.Do(req)
	if err != nil {
		log.Fatal(err)
	}
	res.Body.Close()
	fmt.Printf("Response Code: %s\n", res.Status)

}
C#
/*
 * Copyright (c) 2015 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

/**
 * Sample code to issue several basic Google Cloud Store (GCS) operations
 * using the Google Client Libraries.
 * For more information, see documentation for Compute Storage .NET client
 * https://developers.google.com/api-client-library/dotnet/apis/storage/v1
 *
 * Usage:
 * Add projects references using NuGet to
 * "Google.Apis.Storage.v1"   (other dependencies will get added automatically)
 * http://www.nuget.org/packages/Google.Apis.Storage.v1/
 * Generate and download service account .p12 from place it to c:\
 * specify the bucketName, projectId, serviceAccountEmail and certificateFile below.
 * authorize serviceAccountEmail for your target bucket.
 */

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using Google.Apis;
using Google.Apis.Auth.OAuth2;
using Google.Apis.Download;
using Google.Apis.Services;
using Google.Apis.Storage.v1;
using Google.Apis.Storage.v1.Data;
using Google.Apis.Util.Store;

namespace GCSSearch
{
    internal class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            try
            {
                new Program().Run().Wait();
            }
            catch (AggregateException ex)
            {
                foreach (var err in ex.InnerExceptions)
                {
                    Console.WriteLine("ERROR: " + err.Message);
                }
            }
            Console.ReadKey();
        }

        #region Consts

        private const int KB = 0x400;
        private const int DownloadChunkSize = 256 * KB;

        #endregion

        private async Task Run()
        {
            string projectId = "YOUR_PROJECT_ID";
            string bucketName = "YOUR_BUCKET_NAME";

            //credentials for certificate-based service accounts
            string certificateFile = "c:\\YOUR_CERTIFICATE.p12";
            string serviceAccountEmail = "YOUR_SERVICE_ACCOUNT_EMAIL@developer.gserviceaccount.com";

            var certificate = new X509Certificate2(certificateFile, "notasecret", X509KeyStorageFlags.Exportable);
            ServiceAccountCredential credential = new ServiceAccountCredential(
               new ServiceAccountCredential.Initializer(serviceAccountEmail)
               {
                   Scopes = new[] { StorageService.Scope.DevstorageReadWrite }
               }.FromCertificate(certificate));


            //credentials when running in google compute engine
            //ComputeCredential credential = new ComputeCredential(new ComputeCredential.Initializer());

            //credentials for user interactive webflow
            /*
            UserCredential credential;
            string clientId = "YOUR_CLIENT_ID.apps.googleusercontent.com";
            string clientSecret = "YOUR_CLIENT_SECRET";
            credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(new ClientSecrets{ClientId= clientId,ClientSecret=clientSecret},
                new[] { StorageService.Scope.DevstorageFullControl }, Environment.UserName, CancellationToken.None);
            //Console.WriteLine("Credential file saved at: " + Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData));
             */

            var service = new StorageService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = credential,
                ApplicationName = "GCS Sample",
            });

            Console.WriteLine("List of buckets in current project");
            Buckets buckets = await service.Buckets.List(projectId).ExecuteAsync();

            foreach (var bkt in buckets.Items)
            {
                Console.WriteLine(bkt.Name);
            }

            Console.WriteLine("Total number of items in bucket: " + buckets.Items.Count);
            Console.WriteLine("=============================");

            // using  Google.Apis.Storage.v1.Data.Object to disambiguate from System.Object
            Google.Apis.Storage.v1.Data.Object fileobj = new Google.Apis.Storage.v1.Data.Object() { Name = "somefile.txt" };

            Console.WriteLine("Creating " + fileobj.Name + " in bucket " + bucketName);
            byte[] msgtxt = Encoding.UTF8.GetBytes("Lorem Ipsum");

            using (var streamOut = new MemoryStream(msgtxt))
            {
                await service.Objects.Insert(fileobj, bucketName, streamOut, "text/plain").UploadAsync();
            }

            Console.WriteLine("Object created: " + fileobj.Name);

            Console.WriteLine("=============================");

            Console.WriteLine("Reading object " + fileobj.Name + " in bucket: " + bucketName);
            var req = service.Objects.Get(bucketName, fileobj.Name);
            Google.Apis.Storage.v1.Data.Object readobj = await req.ExecuteAsync();

            Console.WriteLine("Object MediaLink: " + readobj.MediaLink);

            // download using Google.Apis.Download and display the progress
            string pathUser = Environment.GetFolderPath(Environment.SpecialFolder.UserProfile);
            string downloadToDirectory;
            // create the directory and path if the GCS file needs to get saved under a subdirectory
            if (Path.GetDirectoryName(readobj.Name) == String.Empty)
                downloadToDirectory = Path.Combine(pathUser, "Downloads");
            else
                downloadToDirectory = Path.Combine(pathUser, "Downloads", Path.GetDirectoryName(readobj.Name));
            Directory.CreateDirectory(downloadToDirectory);
            var fileName = Path.Combine(downloadToDirectory,Path.GetFileName(readobj.Name));
            Console.WriteLine("Starting download to " + fileName);
            var downloader = new MediaDownloader(service)
            {
                ChunkSize = DownloadChunkSize
            };
            // add a delegate for the progress changed event for writing to console on changes
            downloader.ProgressChanged += Download_ProgressChanged;

            using (var fileStream = new System.IO.FileStream(fileName,
                System.IO.FileMode.Create, System.IO.FileAccess.Write))
            {
                var progress = await downloader.DownloadAsync(readobj.MediaLink, fileStream);
                if (progress.Status == DownloadStatus.Completed)
                {
                    Console.WriteLine(readobj.Name + " was downloaded successfully");
                }
                else
                {
                    Console.WriteLine("Download {0} was interpreted in the middle. Only {1} were downloaded. ",
                        readobj.Name, progress.BytesDownloaded);
                }
            }

            /*
            // or download as a stream
            Stream getStream = await service.HttpClient.GetStreamAsync(readobj.MediaLink);
            Console.WriteLine("Object Content: ");
            using (var reader = new StreamReader(getStream))
            {
                Console.WriteLine(reader.ReadToEnd());
            }
            */
            Console.WriteLine("=============================");
        }

        #region Progress and Response changes

        static void Download_ProgressChanged(IDownloadProgress progress)
        {
            Console.WriteLine(progress.Status + " " + progress.BytesDownloaded + " bytes");
        }

        #endregion
    }
}

Assembling the URL

After you construct the query string and sign it, you need to assemble the URL as follows:

  1. Create the base URL, which refers to the resource, making sure you use the same value as you used in the query string you just signed. Examples:
    • http://storage.googleapis.com/google-testbucket/testdata.txt
    • http://google-testbucket.storage.googleapis.com/testdata.txt
    • https://storage.googleapis.com/google-testbucket/testdata.txt
    • https://google-testbucket.storage.googleapis.com/testdata.txt
  2. URLencode the signature you created. (The Base64 encoded signature may contain characters not legal in URLs (specifically + and /). These values must be replaced by safe encodings (%2B and %2F, respectively).
  3. Concatenate the URL you want to distribute as follows:

    baseURL + "?GoogleAccessId=" + GoogleAccessStorageId + "&Expires=" + Expiration + "&Signature=" + UrlEncodedSignature

    where GoogleAccessStorageId is the email form of the client ID, and Expiration is the same value that you used in the query string you signed.

Here is a sample completed URL:

http://google-testbucket.storage.googleapis.com/testdata.txt?GoogleAccessId=1234567890123@developer.gserviceaccount.com&Expires=1331155464&Signature=BClz9e4UA2MRRDX62TPd8sNpUCxVsqUDG3YGPWvPcwN%2BmWBPqwgUYcOSszCPlgWREeF7oPGowkeKk7J4WApzkzxERdOQmAdrvshKSzUHg8Jqp1lw9tbiJfE2ExdOOIoJVmGLoDeAGnfzCd4fTsWcLbal9sFpqXsQI8IQi1493mw%3D

Creating a signed URL with gsutil

The gsutil tool includes the signurl command to quickly create signed URLs.

To create a signed URL with gsutil:

  1. Make sure you have a Private Key you can use. If you don't, you can follow the steps in Service Account Authentication to create one.
  2. Use the signurl command and specify at least the path to the Private Key and the bucket. For example, you can use the following command to generate a signed URL for users to view the object foo object for 10 minutes.

    $ gsutil signurl -d 10m path/to/privatekey.p12 gs://bucket/foo
    

    The command above will prompt you for the keystore password, which is "notasecret". You can also use the -p flag to specify the password in the command. For help on other options, including how to specify different HTTP methods for which to create the signed URL, display help with gsutil signurl --help.

Back to top