node.jsgoogle-cloud-rungrpc-node

Cloud Run - gRPC authentication through service account - NodeJS


I have to make an authenticated request to a Cloud run service which uses HTTP/2 method for communication via grpc. The service returns a signedURL for a file and it does the same successfully, when deployed unauthenticated. I added the JWT token to the metadata while making request from client side. But the grpc server is giving the following error -

code: 16,
  metadata: Metadata {
    _internal_repr: {
      'www-authenticate': [Array],
      date: [Array],
      server: [Array],
      'x-envoy-upstream-service-time': [Array],
      'content-length': [Array]
    },
    flags: 0
Your client does not have permission to the requested URL

On cloud run, I have enabled HTTP/2 in the connections tab. The service account associated with this Cloud Run service also has Cloud Run Invoker permissions. Following is the code for instantiating the grpc server -

const grpc = require('grpc')
const protoLoader = require('@grpc/proto-loader')
const packageDefinition = protoLoader.loadSync(
  `${__dirname}/${proto_file}`,
  {
    keepCase: true,
    longs: String,
    enums: String,
    defaults: true,
    oneofs: true
  })

const Proto = grpc.loadPackageDefinition(packageDefinition) 
function main () {
  const server = new grpc.Server()
  server.addService(Proto.${service_name}.service, { ${function_name} })
  server.bindAsync(`0.0.0.0:${PORT}`, grpc.ServerCredentials.createInsecure(), (error, port) => {
    if (error) {
      throw error
    }
    server.start()
    console.log('gRPC Server running!')
  })
}
main()

and following is client-side code which is making authenticated requests -

const grpc = require('grpc')
const protoLoader = require('@grpc/proto-loader')
const {GoogleAuth} = require('google-auth-library');
const auth = new GoogleAuth();
const packageDefinition = protoLoader.loadSync(
    `${__dirname}/${proto_file}`, {
      keepCase: true,
      longs: String,
      enums: String,
      defaults: true,
      oneofs: true
    })
//* Or make the changes for authenticated call to CDN here
const Proto = grpc.loadPackageDefinition(packageDefinition)
async function request() {
  return auth.getIdTokenClient(targetAudience);
}

const metadata = new grpc.Metadata();

request().then((token)=> {
  metadata.add('Authorization', `Bearer ${token}`);
})

function getFileLink (projectId, fileId, plaintext = null) {

  const serverAddress = config.server_address
  let credentials
  if (plaintext) {
    credentials = grpc.credentials.createInsecure()
  } else {
    credentials = grpc.credentials.createSsl()
  } 
  const cdn = new cdnProto.CDN(serverAddress, grpc.credentials.createSsl())
  const request = {
    project_id: projectId,
    file_id: fileId
  }
  return new Promise((resolve, reject) => {
    cdn.GetFileLink(request, metadata, (error, response) => {
      if (error) {
        console.log(error)
        resolve(null)
      } else {
        resolve(response)
      }
    })
  })
}


Solution

  • I have resolved this issue. My approach was right, but my way of getting token is incorrect. To get the required auth Token, I have to call another method also.

    async function request() {
      client = await auth.getIdTokenClient(targetAudience);
      return client.idTokenProvider.fetchIdToken(targetAudience)
    }