2
votes

When you create an instance A in Google Compute Engine, it'll get predefined, "default" service account attached to it (this basically means, that you can query google API from A, being authenticated with 'default' service account). What I'd like to do, is to setup GCE instance with service account, that's different than a default one. This should be conceptually possible, given GCE API, but fails with exception:

{ 
"name": "operation-1400060483459-4f958fbc7d7b9-cd817778-b80d1cad",
"operationType": "insert",
"status": "DONE", 
"user": "[email protected]",
"error": { 
   "errors": [ { 
      "code": "SERVICE_ACCOUNT_ACCESS_DENIED", 
      "message": "The user does not have access to service account '[email protected]'"
 } ] } }

Here's my code in python, which setups the instance:

discovery_service = discovery.build('compute',
                config['compute_api_version'],
                http=SignedJwtAssertionCredentials(
                  service_account_name="[email protected]",
                  private_key=key_data,
                  scope='https://www.googleapis.com/auth/compute')
               .authorize(httplib2.Http()))

instance = {}
# sets instance configuration details here
# ...
# ...
instance['serviceAccounts'] = [{
  'email': "[email protected]",
  'scopes': ['https://www.googleapis.com/auth/devstorage.full_control',
              'https://www.googleapis.com/auth/compute',
              'https://www.googleapis.com/auth/userinfo.email', ]
}]
discovery_service.instances().insert(project=project, zone=zone, body=instance)

The weirdest part of it, is that exception says "The user does not have access to service account '[email protected]'", but the "user" it refers to is the '[email protected]' itself! Which means '[email protected]' does not have access to '[email protected]', which makes no sense.

2
Were you able to resolve this issue ?Faizan

2 Answers

1
votes

I believe you'll need to create a new service account to use the API from a non-GCE instance. The service account you're referencing works within a GCE instance only.

  1. To do that go to the Cloud Console > Project > APIs & Auth > Credentials.
  2. Create new Client ID
  3. Service Account
  4. Download the .p12 file and load that as the private key. (See example below)

Also you'll need to create an instance from a boot disk which is typically created from one of the GCE supplied images.

Here's an example using JSON Web Tokens that worked for me. It was adapted from the docs located here: https://cloud.google.com/compute/docs/tutorials/python-guide#addinganinstance.

from apiclient import discovery
from oauth2client.file import Storage
from oauth2client.client import SignedJwtAssertionCredentials
import httplib2
import os.path

INSTANCE_NAME = 'my-instance'
API_VERSION = 'v1'
GCE_URL = 'https://www.googleapis.com/compute/%s/projects/' % (API_VERSION)
PROJECT_ID = '***'
SERVICE_ACOUNT_CLIENT_ID = '***.apps.googleusercontent.com'
SERVICE_ACCOUNT_EMAIL_ADDRESS = '***@developer.gserviceaccount.com'
GCE_SCOPE = 'https://www.googleapis.com/auth/compute'
ZONE = 'us-central1-a'
DEFAULT_SERVICE_EMAIL = 'default'
DEFAULT_SCOPES = ['https://www.googleapis.com/auth/devstorage.full_control',
                  'https://www.googleapis.com/auth/compute']

SOURCE_IMAGE_URL = 'projects/ubuntu-os-cloud/global/images/ubuntu-1410-utopic-v20141217'


def main():
    f = file('private-key.p12', 'rb')
    oauth_key_data = f.read()
    f.close()

    http = httplib2.Http()
    oauth_storage = Storage('compute-creds.dat')
    oauth_credentials = oauth_storage.get()

    if oauth_credentials is None or oauth_credentials.invalid:
        oauth_credentials = SignedJwtAssertionCredentials(
                          service_account_name=SERVICE_ACCOUNT_EMAIL_ADDRESS,
                          private_key=oauth_key_data,
                          scope=GCE_SCOPE)
        oauth_storage.put(oauth_credentials)
    else:
        oauth_credentials.refresh(http)

    http = oauth_credentials.authorize(http)

    gce_service = discovery.build('compute', 'v1', http=http)



    project_url = '%s%s' % (GCE_URL, PROJECT_ID)
    image_url = '%s%s/global/images/%s' % (
             GCE_URL, 'ubuntu-os-cloud', 'ubuntu-1410-utopic-v20141217')
    machine_type_url = '%s/zones/%s/machineTypes/%s' % (
        project_url, ZONE, 'n1-standard-1')
    network_url = '%s/global/networks/%s' % (project_url, 'default')

    instance = {
        'name': INSTANCE_NAME,
        'machineType': machine_type_url,
        'disks': [{
            'autoDelete': 'true',
            'boot': 'true',
            'type': 'PERSISTENT',
            'initializeParams' : {
              'diskName': INSTANCE_NAME,
              'sourceImage': SOURCE_IMAGE_URL
            }
          }],
        'networkInterfaces': [{
          'accessConfigs': [{
            'type': 'ONE_TO_ONE_NAT',
            'name': 'External NAT'
           }],
          'network': network_url
        }],
        'serviceAccounts': [{
             'email': DEFAULT_SERVICE_EMAIL,
             'scopes': DEFAULT_SCOPES
        }]
      }
    # Create the instance
    request = gce_service.instances().insert(
       project=PROJECT_ID, body=instance, zone=ZONE)
    response = request.execute(http=http)
    response = _blocking_call(gce_service, http, response)

    print response

def _blocking_call(gce_service, auth_http, response):
    """Blocks until the operation status is done for the given operation."""

    status = response['status']
    while status != 'DONE' and response:
        operation_id = response['name']

        # Identify if this is a per-zone resource
        if 'zone' in response:
            zone_name = response['zone'].split('/')[-1]
            request = gce_service.zoneOperations().get(
                project=PROJECT_ID,
                operation=operation_id,
                zone=zone_name)
        else:
            request = gce_service.globalOperations().get(
                project=PROJECT_ID, operation=operation_id)

        response = request.execute(http=auth_http)
        if response:
            status = response['status']
    return response

main()
0
votes

FYI: in GCE you usually get two default service accounts:

Note the different Email suffix (developer.gserviceaccount.com vs. cloudservices.gserviceaccount.com). It appears that using your own service account, EVEN if it has the Owner role, does not grant you access to the <number>@cloudservices.gserviceaccount.com account, only to the 1st one (<number>[email protected]).

In my case, I got the aforementioned error when trying to create an instance with my own service account while specifing that the instance will use the 2nd service account from above. Once I fixed the call to request that the instance will use the 1st account, it worked.