google-auth-library-nodejs icon indicating copy to clipboard operation
google-auth-library-nodejs copied to clipboard

How to get default service account client email property programmatically ?

Open rvillane opened this issue 5 years ago • 2 comments

Hi, I have some functionality that currently uses this very old NPM library https://www.npmjs.com/package/google-auto-auth to programmatically retrieve the client email property assigned to the default service account directly, without having to download the credentials JSON file.

It works fine but this google-auto-auth library is very old and has multiple security vulnerabilities reported, not to mention that is no longer maintained. I would like to migrate this functionality to use the official google-auth-library library, but I couldn't find in documentation or examples anything close to my use case.

const rp = require('request-promise-native');
const googleAuth = require('google-auto-auth');
const auth = googleAuth();

function generateTokenForCurrentServiceAccount(ttlObj, host) {
	    return new Promise((resolve, reject) => {
	        // A series of calls into google-auto-auth follow. As of Feb '18 this library does not appear
	        // to easily support promises, even using util.promisify(), so callbacks are used below.
	
	        // Get the credentials object for the current service account, so we can access the email address
	        auth.getCredentials((credErr, credentials) => {
	            if (credErr) {
	                logger.error(`Error in getCredentials step: ${credErr}`);
	                reject(credErr);
	            } else {
	                auth.getProjectId((prjErr, projectId) => {
	                    if (prjErr) {
	                        logger.error(`Error in getProjectId step: ${prjErr}`);
	                        reject(prjErr);
	                    } else {
	                        auth.authorizeRequest({
	                            method: 'post',
	                            uri: `https://iam.googleapis.com/v1/projects/${projectId}/serviceAccounts/${credentials.client_email}:signJwt`,
	                        }, (authErr, authorizedReqOpts) => {
	                            if (authErr) {
	                                logger.error(`Error in authorizeRequest step: ${authErr}`);
	                                reject(authErr);
	                            } else {
	                                const currentTime = moment();
	                                const currentTimeSeconds = currentTime.unix();
	                                const expirationTimeSeconds = currentTimeSeconds + ttlObj.ttlInSeconds;
	
	                                // Form and send web service request to Google to have a JWT claim set signed by the service account.
	                                // This request has the OAuth token in the header.
	
	                                const options = {
	                                    method: 'POST',
	                                    uri: authorizedReqOpts.uri,
	                                    headers: {
	                                        Authorization: authorizedReqOpts.headers.Authorization,
	                                        'content-type': 'application/json',
	                                    },
	                                    body: {
	                                        payload: JSON.stringify({
	                                            iat: currentTimeSeconds,
	                                            exp: expirationTimeSeconds,
	                                            aud: host,
	                                            iss: credentials.client_email,
	                                            sub: credentials.client_email,
	                                        }),
	                                    },
	                                    json: true,
	                                };
	
	                                logger.debug(`Submitting this request to Google: ${JSON.stringify(options.body)}`);
	
	                                rp(options).then(parsedBody => {
	                                    // Successfully signed token, returning it to client
	                                    const token = parsedBody.signedJwt;
	                                    resolve({
	                                        token,
	                                        tokenExpiry: expirationTimeSeconds,
	                                    });
	                                }).catch(err => {
	                                    logger.error(`Error calling GCP to sign JWT: ${err}`);
	                                    reject(err);
	                                });
	                            }
	                        });
	                    }
	                });
	            }
	        });
	    });
	}

can you please point me in the right direction ?

rvillane avatar Nov 30 '19 01:11 rvillane

any thoughts about this question ?

rvillane avatar Mar 19 '20 20:03 rvillane

@rvillane I don't believe we currently expose this property (or at least it would not appear to be public on the class).

I'm not sure if there's a good reason as to why it's private; perhaps you could experiment with running a copy of this library from GitHub, and see if the private field is populated with the value you expect?

bcoe avatar Mar 19 '20 21:03 bcoe

Hi @rvillane, you can get the client email with getClient (using a service account to authenticate), like so (note that this may not work for TS):

const compute = google.compute('v1');

async function main () {
  const auth = new google.auth.GoogleAuth({
    // Scopes can be specified either as an array or as a single, space-delimited string.
    scopes: ['https://www.googleapis.com/auth/compute'],
  });
  const authClient = await auth.getClient();

  console.log(authClient.email);

}

main().catch(console.error); 

However, I think it would be better to use the auth flow listed in the README, you shouldn't need to get the email to authenticate.

sofisl avatar Feb 02 '23 20:02 sofisl