Nodejs-storage: SigningError: Cannot sign data without `client_email`

Created on 9 Jan 2018  路  14Comments  路  Source: googleapis/nodejs-storage

Environment details

  • OS: MacOS
  • Node.js version: 9.2.0
  • npm version: 5.6.0
  • @google-cloud/storage version: 1.5.2

Steps to reproduce

  1. Use gcloud auth application-default login to authenticate
  2. Try to get a signed URL of a file stored on GCS by using storage.bucket('bucket').file('file').getSignedUrl({action: 'read', expires: '10-25-2022'})
  3. This error will happen: SigningError: Cannot sign data without 'client_email'

Thanks!

storage question

Most helpful comment

Does this library work with application default credentials or not? They shouldn't need to be hand-edited.

All 14 comments

gcloud auth application-default login does not seem to set the client_email field, but manually adding it to the local file seems to work

Which local file are you adding it to?

The signinMethod throws incorrectly and leads to an unhandled promise rejection. The full stacktrace is:

Your example code doesn't show either a callback or an error handler being assigned to the returned promise, e.g.

var file = storage.bucket('bucket').file('file')
file.getSignedUrl({action: 'read', expires: '10-25-2022'}, function(err) {})
// or
file.getSignedUrl({action: 'read', expires: '10-25-2022'})
  .then(successHandler, errorHandler)

Could that be the issue?

@stephenplusplus Thanks for your quick answer.
I am adding the client_email field to ~/.config/gcloud/application_default_credentials.json, which only contains:

{
  "client_id": "xxx",
  "client_secret": "yyy",
  "refresh_token": "zzz",
  "type": "authorized_user"
}

Regarding the second issue, I updated my initial post and erased it, but you were too fast!
My exact code is more similar to this

const fn = async () => {
  try{
    const signedUrl = storage
       .bucket('bucket')
       .file('file')
       .getSignedUrl({action: 'read', expires: '10-25-2022'}); // purposedly not awaiting here
    // do other stuff in the mean time
    const url = await signedUrl; // now I need the URL
  }catch(error){
    console.error('Houston...');
  }
};
fn();

But for some reason, this leads to an Unhandled promise rejection. Maybe this is how this is supposed to work and there's actually no problem with GCS. I was unsure about it, and decided to investigate further. But I can't figure why the error is not caught :/

@callmehiphop do you know anything about the error handler error?

Regarding gcloud auth application-default login not holding the client_email property, I don't believe there's anything we can do about that. I think a private key is assigned to a service account, but ADC is user authentication. @alexander-fenster does that sound right?

@callmehiphop re-ping about the unhandled promise rejection error.

@WaldoJeffers I was able to catch the error using your code:

    const fn = async () => {
      try {
        const signedUrl = storage
           .bucket('bucket')
           .file('file')
           .getSignedUrl({action: 'read', expires: '10-25-2022'}); // purposedly not awaiting here

        const url = await signedUrl; // now I need the URL
        console.log(url)
      } catch(error){
        console.error('Houston...', error);
      }
    };

    fn();

Houston... { SigningError: Could not load the default credentials. Browse to https://developers.google.com/accounts/docs/application-default-credentials for more information.
at C:\Users\sawch\dev\nodejs-storage\src\file.js:1715:16
at getCredentials (C:\Users\sawch\dev\nodejs-storage\node_modules\google-auto-auth\index.js:254:9)
at getAuthClient (C:\Users\sawch\dev\nodejs-storage\node_modules\google-auto-auth\index.js:136:9)
at
message: 'Could not load the default credentials. Browse to https://developers.google.com/accounts/docs/application-default-credentials for more information.' }

Is there anything I did wrong in the set-up?

@stephenplusplus Thank you kindly for spending extra time on this, I don't think there's anything wrong with GCS regarding this issue. The async error was not properly caught when I added code between signedUrl and url, but I think this is how it's supposed to work 馃槅
Not having a client_email in ~/.config/gcloud/application_default_credentials.json is definitely a more frustrating issue, as I have to manually edit this file after each reboot :/

That is frustrating. Can you generate a service account key file? That's what we usually support. I'm going to close the issue, as the only thing we can control here is the error handling, and it seems like it's coming back correctly. Let me know if I overlooked anything.

Does this library work with application default credentials or not? They shouldn't need to be hand-edited.

Yes, we do.

The file created by https://cloud.google.com/sdk/gcloud/reference/auth/application-default/login does not have a client_email, which causes this error. The GCS ruby client does not require client_email, so this seems like a bug or something that should be implemented?

I see, and I suppose while our methods do support ADC, this method does not. ADC within a GCP environment works with this method, and ADC via setting the GOOGLE_APPLICATION_CREDENTIALS environment variable to point to a service account JSON file will work. But, we can't sign data without a client_email, and it seems like this form of authentication (gcloud auth application-default login) does not return one.

The GCS ruby client does not require client_email, so this seems like a bug or something that should be implemented?

I tried, but couldn't get it to work. I would receive an error until I specified my service account JSON file. Could you share more about how you did that?

In a Pry ruby console:

[7] pry(main)> require "google/cloud/storage"
=> true
[8] pry(main)> storage = Google::Cloud::Storage.new(project_id: "my-project")

=> #<Google::Cloud::Storage::Project:0x00007faead434e10
 @service=Google::Cloud::Storage::Service(my-project)>
[9] pry(main)> storage.buckets
=> [#<Google::Cloud::Storage::Bucket:0x00007faeaf90b130
  @gapi=
   #<Google::Apis::StorageV1::Bucket:0x00007faead391148
    @etag="CAE=",
    @id="artifacts.my-project.appspot.com",
    @kind="storage#bucket",
    @location="US",
    @metageneration=1,
    @name="artifacts.my-project.appspot.com",
    @project_number=snip,
    @self_link=
     "https://www.googleapis.com/storage/v1/b/artifacts.my-project.appspot.com",
    @storage_class="STANDARD",
    @time_created=
     #<DateTime: 2018-01-05T17:57:36+00:00 ((2458124j,64656s,404000000n),+0s,2299161j)>,
    @updated=
     #<DateTime: 2018-01-05T17:57:36+00:00 ((2458124j,64656s,404000000n),+0s,2299161j)>>,
  @service=Google::Cloud::Storage::Service(my-project),
  @user_project=nil>,
...
[10] pry(main)>

I used dtruss to inspect the system calls that are being issued by these commands and found that it reads from /Users/myuser/.config/gcloud/application_default_credentials.json as expected

That will work in our API as well. Specifically, the only thing we can鈥檛 do without a client_email is sign a URL or policy.

Apologies, you are correct. Sorry for wasting your time!

Was this page helpful?
0 / 5 - 0 ratings