<p>I'm trying get image files of Google Cloud Storage (GCS) in my Node.js application using Axios client. On develop mode using my PC I pass a Bearer Token and all works properly.</p>
<p>But, I need to use this in production in a cluster hosted on Google Kubernetes Engine (GKE).</p>
<p>I made recommended tuturials to create a service account (GSA), then I vinculed with kubernetes account (KSA), via Workload identity approach, but when I try get files througt one endpoint on my app, I'm receiving:</p>
<pre><code>{&quot;statusCode&quot;:401,&quot;message&quot;:&quot;Unauthorized&quot;}
</code></pre>
<p>What is missing to make?</p>
<hr />
<h2>Update: What I've done:</h2>
<ol>
<li>Create Google Service Account</li>
</ol>
<p><a href="https://cloud.google.com/iam/docs/creating-managing-service-accounts" rel="noreferrer">https://cloud.google.com/iam/docs/creating-managing-service-accounts</a></p>
<ol start="2">
<li>Create Kubernetes Service Account</li>
</ol>
<pre><code># gke-access-gcs.ksa.yaml file

apiVersion: v1
kind: ServiceAccount
metadata:
  name: gke-access-gcs
</code></pre>
<pre><code>kubectl apply -f gke-access-gcs.ksa.yaml
</code></pre>
<ol start="3">
<li>Relate KSAs and GSAs</li>
</ol>
<pre><code>gcloud iam service-accounts add-iam-policy-binding \
  --role roles/iam.workloadIdentityUser \
  --member &quot;serviceAccount:cluster_project.svc.id.goog[k8s_namespace/ksa_name]&quot; \
  gsa_name@gsa_project.iam.gserviceaccount.com
</code></pre>
<ol start="4">
<li>Note the KSA and complete the link between KSA and GSA</li>
</ol>
<pre><code>kubectl annotate serviceaccount \
  --namespace k8s_namespace \
   ksa_name \
   iam.gke.io/gcp-service-account=gsa_name@gsa_project.iam.gserviceaccount.com
</code></pre>
<ol start="5">
<li>Set Read and Write role:</li>
</ol>
<pre><code>gcloud projects add-iam-policy-binding project-id \
--member=serviceAccount:gsa-account@project-id.iam.gserviceaccount.com \
--role=roles/storage.objectAdmin
</code></pre>
<ol start="6">
<li>Test access:</li>
</ol>
<pre><code>kubectl run -it \
  --image google/cloud-sdk:slim \
  --serviceaccount ksa-name \
  --namespace k8s-namespace \
  workload-identity-test
</code></pre>
<p>The above command works correctly. Note that was passed <code>--serviceaccount</code> and <code>workload-identity</code>. Is this necessary to GKE?</p>
<p>PS: I don't know if this influences, but I am using SQL Cloud with proxy in the project.</p>

<h3>EDIT</h3>
<p>Issue portrayed in the question is related to the fact that axios client <strong>does not use</strong> the Application Default Credentials (as official Google libraries) mechanism that <a href="https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity" rel="noreferrer">Workload Identity</a> takes advantage of.  The ADC checks:</p>
<blockquote>
<ul>
<li>If the environment variable  <code>GOOGLE_APPLICATION_CREDENTIALS</code>  is set, ADC uses the service account file that the variable points to.</li>
<li>If the environment variable  <code>GOOGLE_APPLICATION_CREDENTIALS</code>  isn't set, ADC uses the default service account that Compute Engine, Google Kubernetes Engine, App Engine, Cloud Run, and Cloud Functions provide.</li>
</ul>
<p>-- <em><a href="https://cloud.google.com/docs/authentication/production#auth-cloud-implicit-nodejs" rel="noreferrer">Cloud.google.com: Authentication: Production </a></em></p>
</blockquote>
<p><strong>This means that axios client will need to fall back to the <code>Bearer token</code> authentication method to authenticate against Google Cloud Storage.</strong></p>
<p>The authentication with <code>Bearer token</code> is described in the official documentation as following:</p>
<blockquote>
<h3>API authentication</h3>
<p>To make requests using OAuth 2.0 to either the Cloud Storage  <a href="https://cloud.google.com/storage/docs/xml-api/overview" rel="noreferrer">XML API</a>  or  <a href="https://cloud.google.com/storage/docs/json_api/v1" rel="noreferrer">JSON API</a>, include your application's access token in the  <code>Authorization</code>  header in every request that requires authentication. You can generate an access token from the  <a href="https://developers.google.com/oauthplayground/" rel="noreferrer">OAuth 2.0 Playground</a>.</p>
<pre><code>Authorization: Bearer OAUTH2_TOKEN
</code></pre>
<p>The following is an example of a request that lists objects in a bucket.</p>
<blockquote>
<p><a href="https://cloud.google.com/storage/docs/authentication#json-api" rel="noreferrer">JSON API</a></p>
<p>Use the  <a href="https://cloud.google.com/storage/docs/json_api/v1/objects/list" rel="noreferrer">list</a>  method of the Objects resource.</p>
<pre><code>GET /storage/v1/b/example-bucket/o HTTP/1.1
Host: www.googleapis.com
Authorization: Bearer ya29.AHES6ZRVmB7fkLtd1XTmq6mo0S1wqZZi3-Lh_s-6Uw7p8vtgSwg
</code></pre>
</blockquote>
<p>-- <a href="https://cloud.google.com/storage/docs/authentication#apiauth" rel="noreferrer">Cloud.google.com: Storage: Docs: Api authentication</a></p>
</blockquote>
<hr />
<p>I've included <strong>basic example</strong> of a code snippet using Axios to query the Cloud Storage (requires <code>$ npm install axios</code>):</p>
<pre class="lang-js prettyprint-override"><code>const Axios = require('axios');

const config = {
    headers: { Authorization: 'Bearer ${OAUTH2_TOKEN}' }
};

Axios.get( 
  'https://storage.googleapis.com/storage/v1/b/BUCKET-NAME/o/',
  config
).then(
  (response) =&gt; {
    console.log(response.data.items);
  },
  (err) =&gt; {
    console.log('Oh no. Something went wrong :(');
    // console.log(err) &lt;-- Get the full output!
  }
);
</code></pre>
<p>I left below example of Workload Identity setup with a node.js official library code snippet as it could be useful to other community members.</p>
<hr />
<p>Posting this answer as I've managed to use <code>Workload Identity</code> and a simple <code>nodejs</code> app to send and retrieve data from <code>GCP bucket</code>.</p>
<p>I included some bullet points for troubleshooting potential issues.</p>
<hr />
<h2>Steps:</h2>
<ul>
<li>Check if <code>GKE</code> cluster has <code>Workload Identity</code> enabled.</li>
<li>Check if your <code>Kubernetes service account</code> is associated with your <code>Google Service account</code>.</li>
<li>Check if example workload is using correct <code>Google Service account</code> when connecting to the API's.</li>
<li>Check if your <code>Google Service account</code> is having correct permissions to access your <code>bucket</code>.</li>
</ul>
<p>You can also follow the official documentation:</p>
<ul>
<li><em><a href="https://cloud.google.com/kubernetes-engine/docs/how-to/workload-identity" rel="noreferrer">Cloud.google.com: Kubernetes Engine: Workload Identity</a></em></li>
</ul>
<hr />
<p>Assuming that:</p>
<ul>
<li>Project (ID) named: <code>awesome-project</code> &lt;- <strong>it's only example</strong></li>
<li>Kubernetes namespace named: <code>bucket-namespace</code></li>
<li>Kubernetes service account named: <code>bucket-service-account</code></li>
<li>Google service account named: <code>google-bucket-service-account</code></li>
<li>Cloud storage bucket named: <code>workload-bucket-example</code> &lt;- <strong>it's only example</strong></li>
</ul>
<p>I've included the commands:</p>
<pre class="lang-sh prettyprint-override"><code>$ kubectl create namespace bucket-namespace
$ kubectl create serviceaccount --namespace bucket-namespace bucket-service-account
$ gcloud iam service-accounts create google-bucket-service-account
$ gcloud iam service-accounts add-iam-policy-binding --role roles/iam.workloadIdentityUser --member &quot;serviceAccount:awesome-project.svc.id.goog[bucket-namespace/bucket-service-account]&quot; google-bucket-service-account@awesome-project.iam.gserviceaccount.com
$ kubectl annotate serviceaccount --namespace bucket-namespace bucket-service-account iam.gke.io/gcp-service-account=google-bucket-service-account@awesome-project-ID.iam.gserviceaccount.com
</code></pre>
<p>Using the guide linked above check the service account authenticating to API's:</p>
<ul>
<li><code>$ kubectl run -it --image google/cloud-sdk:slim --serviceaccount bucket-service-account --namespace bucket-namespace workload-identity-test</code></li>
</ul>
<p>The output of <code>$ gcloud auth list</code> should show:</p>
<pre class="lang-sh prettyprint-override"><code>                           Credentialed Accounts
ACTIVE  ACCOUNT
*       google-bucket-service-account@AWESOME-PROJECT.iam.gserviceaccount.com

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
</code></pre>
<blockquote>
<p>Google service account created earlier should be present in the output!</p>
</blockquote>
<p>Also it's required to add the permissions for the service account to the bucket. You can either:</p>
<ul>
<li>Use <code>Cloud Console</code></li>
<li>Run: <code>$ gsutil iam ch serviceAccount:google-bucket-service-account@awesome-project.iam.gserviceaccount.com:roles/storage.admin gs://workload-bucket-example</code></li>
</ul>
<p>To download the file from the <code>workload-bucket-example</code> following code can be used:</p>
<pre class="lang-js prettyprint-override"><code>// Copyright 2020 Google LLC

/**
 * This application demonstrates how to perform basic operations on files with
 * the Google Cloud Storage API.
 *
 * For more information, see the README.md under /storage and the documentation
 * at https://cloud.google.com/storage/docs.
 */
const path = require('path');
const cwd = path.join(__dirname, '..');

function main(
  bucketName = 'workload-bucket-example',
  srcFilename = 'hello.txt',
  destFilename = path.join(cwd, 'hello.txt')
) {
  const {Storage} = require('@google-cloud/storage');

  // Creates a client
  const storage = new Storage();

  async function downloadFile() {
    const options = {
      // The path to which the file should be downloaded, e.g. &quot;./file.txt&quot;
      destination: destFilename,
    };

    // Downloads the file
    await storage.bucket(bucketName).file(srcFilename).download(options);

    console.log(
      `gs://${bucketName}/${srcFilename} downloaded to ${destFilename}.`
    );
  }

  downloadFile().catch(console.error);
  // [END storage_download_file]
}
main(...process.argv.slice(2));
</code></pre>
<p>The code is exact copy from:</p>
<ul>
<li><em><a href="https://googleapis.dev/nodejs/storage/latest/" rel="noreferrer">Googleapis.dev: NodeJS: Storage</a></em></li>
<li><em><a href="https://github.com/googleapis/nodejs-storage/blob/master/samples/downloadFile.js" rel="noreferrer">Github.com: Googleapis: Nodejs-storage: downloadFile.js</a></em></li>
</ul>
<p>Running this code should produce an output:</p>
<pre class="lang-sh prettyprint-override"><code>root@ubuntu:/# nodejs app.js 
gs://workload-bucket-example/hello.txt downloaded to /hello.txt.
</code></pre>
<pre class="lang-sh prettyprint-override"><code>root@ubuntu:/# cat hello.txt 
Hello there!
</code></pre>
