<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Cloud Ops Chronicles - en</title><link href="https://www.ops-chronicles.cloud/" rel="alternate"/><link href="https://www.ops-chronicles.cloud/feeds/en.atom.xml" rel="self"/><id>https://www.ops-chronicles.cloud/</id><updated>2026-07-05T00:00:00+02:00</updated><entry><title>"Goodbye GCP JSON Keys: Secure Hybrid Authentication via WIF and TPM/Secure Enclave Chips"</title><link href="https://www.ops-chronicles.cloud/goodbye-gcp-json-keys-secure-hybrid-authentication-via-wif-and-tpmsecure-enclave-chips.html" rel="alternate"/><published>2026-07-05T00:00:00+02:00</published><updated>2026-07-05T00:00:00+02:00</updated><author><name>Mathieu GOULIN</name></author><id>tag:www.ops-chronicles.cloud,2026-07-05:/goodbye-gcp-json-keys-secure-hybrid-authentication-via-wif-and-tpmsecure-enclave-chips.html</id><summary type="html">&lt;p&gt;&amp;ldquo;Remove static Service Account JSON keys by replacing them with hardware-based TPM/Secure Enclave authentication using GCP Workload Identity Federation.&amp;rdquo;&lt;/p&gt;</summary><content type="html">&lt;p&gt;Authenticating on-premises servers or local workstations to cloud providers (specifically Google Cloud) has historically relied on exporting Service Account keys (in JSON format for Google). However, storing static secrets on local hard drives presents major security risks (accidental leaks, lack of rotation, complex auditability).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The &amp;ldquo;Why Now?&amp;rdquo;&lt;/strong&gt;: Google now actively recommends abandoning Service Account keys in favor of federated identity solutions such as &lt;a href="https://cloud.google.com/iam/docs/workload-identity-federation"&gt;Workload Identity Federation (WIF)&lt;/a&gt;. This transition is driven by &lt;a href="https://docs.cloud.google.com/iam/docs/best-practices-service-accounts#use-keys"&gt;Google&amp;rsquo;s security best practices&lt;/a&gt; and facilitated by GCP organization policies (specifically the constraint &lt;a href="https://cloud.google.com/iam/docs/org-policy-constraints#disable-service-account-key-creation"&gt;&lt;code&gt;iam.disableServiceAccountKeyCreation&lt;/code&gt;&lt;/a&gt;) that allow organizations to completely block the creation of new static service account keys.&lt;/p&gt;
&lt;p&gt;This article presents an architecture to completely eliminate these JSON keys by replacing them with a temporary cryptographic identity, anchored in the physical chip of your machines (TPM 2.0 or Secure Enclave) and validated by &lt;strong&gt;GCP Workload Identity Federation (WIF)&lt;/strong&gt; in a fully serverless manner.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Summary&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In this article, you will learn how to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Permanently remove Service Account JSON keys from your local servers and workstations.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use the physical security chip (TPM 2.0 or Secure Enclave) of your machines as a root of trust.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configure GCP Workload Identity Federation (WIF) to validate temporary security tokens.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Provision this architecture using Terraform.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Deploy this configuration in &lt;strong&gt;Daemonless&lt;/strong&gt; mode (via Google&amp;rsquo;s Executable Credentials) or via a local emulator.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Prepare for a future evolution towards the &lt;strong&gt;SPIFFE/SPIRE&lt;/strong&gt; identity standard.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2 id="the-big-picture-global-vision"&gt;The &amp;ldquo;Big Picture&amp;rdquo; (Global Vision)&lt;a class="headerlink" href="#the-big-picture-global-vision" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As introduced, the core idea is to use the hardware-protected cryptographic key (TPM or Secure Enclave) to sign a temporary token (JWT) that can be exchanged for a short-lived GCP token.&lt;/p&gt;
&lt;p&gt;Here is the simplified diagram of the authentication flow:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;sequenceDiagram&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;participant&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Machine&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Local&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Machine&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;br&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TPM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Enclave&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;participant&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;STS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GCP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Security&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;br&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Service&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;participant&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GCS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GCS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;br&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;JWKS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;participant&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Target&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GCS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Bucket&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;br&lt;/span&gt;&lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Backups&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Machine&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Machine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Generates&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Signs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;JWT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Private&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Machine&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;STS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Sends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;signed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;JWT&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;STS&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;GCS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Downloads&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Public&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;JWKS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;GCS&lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;STS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Returns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;jwks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;STS&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;STS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Verifies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;signature&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;STS&lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Machine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Returns&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;short&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lived&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GCP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Machine&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Secure&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GCP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Token&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Target&lt;/span&gt;&lt;span class="o"&gt;--&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;Machine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Success&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;In summary, the flow works as follows:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The client machine generates a temporary token (JWT) locally.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It signs this JWT using its private key, which remains confined within its physical chip.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It transmits the signed JWT to Google Cloud (STS).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Google verifies the signature by fetching the machine&amp;rsquo;s public key (statically hosted on a public GCS bucket).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Google validates the identity and returns a short-lived GCP access token.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The machine uses this token to execute its tasks.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;em&gt;Typical use case&lt;/em&gt;: Allowing administrators to schedule automatic backup scripts to GCS, without ever storing passwords or API keys on the servers.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="access-methods-comparison-table"&gt;Access Methods Comparison Table&lt;a class="headerlink" href="#access-methods-comparison-table" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As shown in the following table, the physical chip method provides the highest level of security and flexibility:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left;"&gt;Solution&lt;/th&gt;
&lt;th style="text-align: center;"&gt;Key on disk&lt;/th&gt;
&lt;th style="text-align: center;"&gt;Key rotation&lt;/th&gt;
&lt;th style="text-align: center;"&gt;Hardware root of trust&lt;/th&gt;
&lt;th style="text-align: center;"&gt;Deployment complexity&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;Classic JSON Key&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: center;"&gt;Yes ❌&lt;/td&gt;
&lt;td style="text-align: center;"&gt;Manual / Complex ❌&lt;/td&gt;
&lt;td style="text-align: center;"&gt;No ❌&lt;/td&gt;
&lt;td style="text-align: center;"&gt;⭐ (Easy but risky)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;Classic WIF (AWS/Azure/Okta)&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: center;"&gt;No ✅&lt;/td&gt;
&lt;td style="text-align: center;"&gt;Automatic ✅&lt;/td&gt;
&lt;td style="text-align: center;"&gt;Partial (depends on IdP)&lt;/td&gt;
&lt;td style="text-align: center;"&gt;⭐⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;WIF + Hardware Key (TPM / Enclave)&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: center;"&gt;No ✅&lt;/td&gt;
&lt;td style="text-align: center;"&gt;Automatic ✅&lt;/td&gt;
&lt;td style="text-align: center;"&gt;&lt;strong&gt;Yes (Physical chip) ✅&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: center;"&gt;⭐⭐⭐⭐&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left;"&gt;&lt;strong&gt;SPIFFE / SPIRE&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: center;"&gt;No ✅&lt;/td&gt;
&lt;td style="text-align: center;"&gt;Automatic ✅&lt;/td&gt;
&lt;td style="text-align: center;"&gt;&lt;strong&gt;Yes (Physical chip) ✅&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: center;"&gt;⭐⭐⭐⭐⭐ (Heavy: requires direct TCP agent/server access via Network Load Balancer, restricting hosting to GCE or GKE)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2 id="when-to-use-this-architecture"&gt;When to use this architecture?&lt;a class="headerlink" href="#when-to-use-this-architecture" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="ideal-for"&gt;✅ Ideal for:&lt;a class="headerlink" href="#ideal-for" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Automated on-premises backups&lt;/strong&gt;: Securely authenticating local servers to a Google Cloud Storage bucket.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Edge Computing &amp;amp; IoT&lt;/strong&gt;: Identifying physical machines or industrial gateways isolated in the field.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Admin workstations (Mac/Linux/Windows)&lt;/strong&gt;: Allowing administrators to set up automated tasks (maintenance scripts, scheduled syncs, etc.) that execute without their personal credentials. For interactive operations, the best practice remains to authenticate via &lt;code&gt;gcloud auth login&lt;/code&gt; — this architecture specifically targets scenarios where no human is present to re-enter credentials.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Partially isolated (Air-Gapped) environments&lt;/strong&gt;: Servers only need outbound access to Google APIs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="not-suited-for"&gt;❌ Not suited for:&lt;a class="headerlink" href="#not-suited-for" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Kubernetes workloads (GKE / on-premises)&lt;/strong&gt;: Use native Kubernetes identity (&lt;em&gt;GKE Workload Identity&lt;/em&gt;) or the &lt;strong&gt;SPIFFE/SPIRE&lt;/strong&gt; framework instead to manage identities at scale.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Virtual machines on Google Compute Engine&lt;/strong&gt;: Use native service accounts attached to instances transparently.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Managed cloud services (Cloud Run, Cloud Functions)&lt;/strong&gt;: Leverage native service account impersonation built into GCP runtimes.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2 id="1-architecture-and-components"&gt;1. Architecture and Components&lt;a class="headerlink" href="#1-architecture-and-components" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To validate the signed JWT assertion generated by the client machine&amp;rsquo;s chip, Google Cloud needs access to that machine&amp;rsquo;s public key. Rather than deploying and maintaining an active, expensive identity provider (like Okta or a Keycloak server), we use a fully &lt;strong&gt;serverless, minimal-cost&lt;/strong&gt; model:&lt;/p&gt;
&lt;p&gt;&lt;img alt="WIF TPM Architecture Diagram" src="/images/2026-Serverless_onprem_wif_hardware_auth/architecture.png" /&gt;&lt;/p&gt;
&lt;p&gt;It relies specifically on:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;A Public Cloud Storage bucket&lt;/strong&gt; hosting two static OIDC files:&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.well-known/openid-configuration&lt;/code&gt;: Describes our minimalist identity provider.&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;jwks.json&lt;/code&gt;: Contains the public keys (JSON Web Keys) of the authorized machines.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A Workload Identity Pool &amp;amp; Provider (OIDC)&lt;/strong&gt; configured in GCP to trust our GCS bucket as the identity issuer (Issuer URL).&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;RFC Standards Used&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;jwks.json&lt;/code&gt; file complies with the RFC 7517 standard (JSON Web Key). The signed assertion complies with the RFC 7515 standard (JSON Web Signature). The final token exchange with the Google STS API (&lt;code&gt;https://sts.googleapis.com/v1/token&lt;/code&gt;) is governed by the RFC 8693 standard (OAuth 2.0 Token Exchange).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonition important"&gt;
&lt;p class="admonition-title"&gt;JWKS Caching by Google&lt;/p&gt;
&lt;p&gt;Google STS does not download the &lt;code&gt;jwks.json&lt;/code&gt; file for every token request. GCP heavily caches the OpenID Configuration document and the JWKS to limit load on the issuing server. Therefore, when adding or rotating a key, you must expect a propagation delay (the exact TTL is not documented by Google) before the new key becomes usable.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="admonition-title"&gt;Impact on Revocation&lt;/p&gt;
&lt;p&gt;This caching mechanism has a critical security consequence: if a machine is compromised and you remove its public key from &lt;code&gt;jwks.json&lt;/code&gt;, revocation will not be immediate. Until Google&amp;rsquo;s cache expires, an attacker possessing the private key could continue to obtain valid tokens. This is currently the main limitation of this architecture. Hopefully, Google will provide a cache purge mechanism or explicit revocation on the WIF side in the future.&lt;/p&gt;
&lt;/div&gt;
&lt;hr /&gt;
&lt;h2 id="2-terraform-configuration-provisioning"&gt;2. Terraform Configuration (Provisioning)&lt;a class="headerlink" href="#2-terraform-configuration-provisioning" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that the principle is clear, here is how to set it up with Terraform.&lt;/p&gt;
&lt;p&gt;Below is the Terraform code that configures the static OIDC issuer on GCS, the WIF Pool, compiles the public keys of the machines, and restricts Service Account impersonation to registered machines.&lt;/p&gt;
&lt;p&gt;The structure of the Terraform directory is as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;terraform&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tf&lt;/span&gt;&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="c1"&gt;# Main logic: public OIDC bucket, backup bucket, SA, WIF Pool, OIDC Provider,&lt;/span&gt;
&lt;span class="err"&gt;│&lt;/span&gt;&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="c1"&gt;#   JWKS compilation, and IAM bindings per machine&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;variables&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tf&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="c1"&gt;# Input variables: project_id, project_number, region, and jwks_dir (path to public keys)&lt;/span&gt;
&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;outputs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tf&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="c1"&gt;# Exported outputs: bucket names, SA email, WIF Pool ID, issuer URL, and registered machines&lt;/span&gt;
&lt;span class="err"&gt;└──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;jwks&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="c1"&gt;# One .json file per machine (JWK public key)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;├──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;macbook&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;pro&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;└──&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;srv&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;backup&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;01.j&lt;/span&gt;&lt;span class="n"&gt;son&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;The key point of this approach: &lt;strong&gt;Terraform automatically reads all files in the &lt;code&gt;jwks/&lt;/code&gt; directory&lt;/strong&gt;, compiles an aggregated &lt;code&gt;jwks.json&lt;/code&gt;, generates the &lt;code&gt;.well-known/openid-configuration&lt;/code&gt; document, uploads both to GCS, and creates an IAM binding per machine:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c1"&gt;# Extract from terraform/main.tf — JWKS compilation and multi-machine IAM&lt;/span&gt;
&lt;span class="nb"&gt;locals&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;jwk_files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;fileset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;var.jwks_dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;*.json&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;jwk_keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;local.jwk_files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;jsondecode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;${var.jwks_dir}/${f}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;jwks_json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;local.jwk_keys&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nb"&gt;machine_fingerprints&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;local.jwk_files&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nf"&gt;trimsuffix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;.json&amp;quot;) =&amp;gt; jsondecode(file(&amp;quot;${var.jwks_dir}/${f}&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="err"&gt;kid&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# Upload compiled JWKS to GCS&lt;/span&gt;
&lt;span class="kr"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;&amp;quot;google_storage_bucket_object&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;&amp;quot;jwks_json&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;jwks.json&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;bucket&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;google_storage_bucket.jwks_bucket.name&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;local.jwks_json&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;content_type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;application/json&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;# One IAM binding per machine, linked to its key fingerprint&lt;/span&gt;
&lt;span class="kr"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nc"&gt;&amp;quot;google_service_account_iam_member&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;&amp;quot;wif_sa_impersonation&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;for_each&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;local.machine_fingerprints&lt;/span&gt;

&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;service_account_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;google_service_account.backup_sa.name&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="w"&gt;               &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;roles/iam.workloadIdentityUser&amp;quot;&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="na"&gt;member&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;principal://iam.googleapis.com/projects/${var.project_number}/locations/global/workloadIdentityPools/${google_iam_workload_identity_pool.hardware_pool.workload_identity_pool_id}/subject/machine-${each.value}&amp;quot;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To add a new machine, simply drop its JWK file in &lt;code&gt;terraform/jwks/&lt;/code&gt; and run &lt;code&gt;terraform apply&lt;/code&gt;. To revoke a machine, delete its file and re-run — Terraform will remove its key from the JWKS and delete its IAM binding.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="3-hardware-signing-logic-clients"&gt;3. Hardware Signing Logic (Clients)&lt;a class="headerlink" href="#3-hardware-signing-logic-clients" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="the-jwt-hashing-and-signing-pipeline"&gt;The JWT Hashing and Signing Pipeline&lt;a class="headerlink" href="#the-jwt-hashing-and-signing-pipeline" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To authenticate with Google WIF, the local client constructs a temporary security token (JWT) containing the mandatory claims: &lt;code&gt;iss&lt;/code&gt; (Issuer), &lt;code&gt;aud&lt;/code&gt; (Audience), &lt;code&gt;sub&lt;/code&gt; (Unique subject based on the public key fingerprint), &lt;code&gt;iat&lt;/code&gt; (issued at time), and &lt;code&gt;exp&lt;/code&gt; (expiration time, set to 5 minutes).&lt;/p&gt;
&lt;p&gt;The JWT Header and Payload are Base64URL encoded and concatenated with a dot. This string is hashed via &lt;strong&gt;SHA-256&lt;/strong&gt;, and the resulting 32-byte digest is sent to the physical processor for signing using the internal private key (&lt;strong&gt;ECDSA P-256 / ES256&lt;/strong&gt;).&lt;/p&gt;
&lt;h4 id="format-conversion-der-tpm-to-raw-r-s-jwt"&gt;Format Conversion: DER (TPM) to Raw R || S (JWT)&lt;a class="headerlink" href="#format-conversion-der-tpm-to-raw-r-s-jwt" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Hardware chips (like TPMs) typically return signatures in the standardized ASN.1 DER format. This format encapsulates the two integers $R$ and $S$ of the ECDSA signature in a binary structure. To comply with the JWT standard (ES256), our client code extracts these two integers and concatenates them as a flat 64-byte block ($R$ padded to 32 bytes || $S$ padded to 32 bytes).&lt;/p&gt;
&lt;h3 id="a-macos-swift-cryptokit-secure-enclave"&gt;A. macOS: Swift &amp;amp; CryptoKit (Secure Enclave)&lt;a class="headerlink" href="#a-macos-swift-cryptokit-secure-enclave" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;On macOS, we use &lt;code&gt;CryptoKit&lt;/code&gt; to generate a private key inside the Secure Enclave.
The complete source code is available in &lt;a href="https://gitlab.com/matgou/gcp-workload-identification/-/blob/main/client-swift/main.swift"&gt;client-swift/main.swift&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The private key is never exportable through public software APIs (it is protected by the physical security processor). To reuse the same key, the program saves a wrapped secure representation of the key (&lt;code&gt;key.wrapped&lt;/code&gt;). This file is encrypted by the current machine&amp;rsquo;s Secure Enclave and is completely useless if copied to another computer.&lt;/p&gt;
&lt;h3 id="b-linux-go-go-tpm-tpm-20"&gt;B. Linux: Go &amp;amp; go-tpm (TPM 2.0)&lt;a class="headerlink" href="#b-linux-go-go-tpm-tpm-20" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;For Linux servers equipped with a TPM 2.0, the implementation relies on the standard TPM protocol and uses the official &lt;code&gt;go-tpm v0.3.3&lt;/code&gt; library. The complete code is written in &lt;a href="https://gitlab.com/matgou/gcp-workload-identification/-/blob/main/client-tpm/main.go"&gt;client-tpm/main.go&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To avoid key collisions with other local software, the TPM persistent storage handle is not hardcoded but configurable using the &lt;code&gt;-tpm-handle&lt;/code&gt; option.&lt;/p&gt;
&lt;div class="admonition warning"&gt;
&lt;p class="admonition-title"&gt;Multi-user Isolation on Linux (TPM)&lt;/p&gt;
&lt;p&gt;Please note that the TPM does not provide native isolation per Unix user. A key stored at a persistent handle is accessible to any process with read permissions on the &lt;code&gt;/dev/tpmrm0&lt;/code&gt; device. On a multi-user server, another local account could therefore use this key to authenticate with GCP.&lt;/p&gt;
&lt;p&gt;TPM 2.0 resolves this issue by associating an &lt;strong&gt;authorization policy&lt;/strong&gt; with the key (password, PCR policy, or HMAC session). While fully implementable, this protection is not covered in this article to keep the implementation simple and focused on the WIF flow. For a multi-user production environment, this is an important additional step to consider.&lt;/p&gt;
&lt;/div&gt;
&lt;h3 id="c-windows-powershell-cng-tpm-20"&gt;C. Windows: PowerShell &amp;amp; CNG (TPM 2.0)&lt;a class="headerlink" href="#c-windows-powershell-cng-tpm-20" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;On Windows, we can interact with the TPM 2.0 using the &lt;strong&gt;Microsoft Platform Crypto Provider&lt;/strong&gt; via PowerShell and .NET&amp;rsquo;s Cryptography Next Generation (CNG) APIs. &lt;/p&gt;
&lt;p&gt;The private key is generated and stored inside the TPM. The signature returned by the Windows CNG API is already in the raw $R \parallel S$ format (64 bytes), meaning no DER-to-RAW conversion is required.&lt;/p&gt;
&lt;p&gt;Here is the PowerShell script to generate a TPM-backed key and export its public key in JWK format:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;# Create a TPM-backed ECDsa P-256 key&lt;/span&gt;
&lt;span class="nv"&gt;$keyName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;gcp-wif-backup-key&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;$keyParams&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;New-Object&lt;/span&gt; &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Security&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cryptography&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CngKeyCreationParameters&lt;/span&gt;
&lt;span class="nv"&gt;$keyParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Provider&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Microsoft Platform Crypto Provider&amp;quot;&lt;/span&gt;
&lt;span class="nv"&gt;$keyParams&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;KeyCreationOptions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="no"&gt;[System.Security.Cryptography.CngKeyCreationOptions]&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;OverwriteExistingKey&lt;/span&gt;

&lt;span class="nv"&gt;$cngKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="no"&gt;[System.Security.Cryptography.CngKey]&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="no"&gt;[System.Security.Cryptography.CngAlgorithm]&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ECDsaP256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;$keyName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;$keyParams&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Export the public key (CNG ECC Public Blob format)&lt;/span&gt;
&lt;span class="nv"&gt;$pubBlob&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$cngKey&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Export&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;[System.Security.Cryptography.CngKeyBlobFormat]&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;EccPublicBlob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Extract X and Y coordinates (32 bytes each, starting after the 8-byte header)&lt;/span&gt;
&lt;span class="nv"&gt;$xBytes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$pubBlob&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;8&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="n"&gt;39&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nv"&gt;$yBytes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$pubBlob&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;40&lt;/span&gt;&lt;span class="p"&gt;..&lt;/span&gt;&lt;span class="n"&gt;71&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="c"&gt;# Base64URL encoding helper&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;Safe-Base64Url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;[byte[]]&lt;/span&gt;&lt;span class="nv"&gt;$bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="no"&gt;[Convert]&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ToBase64String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$bytes&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;+&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;-&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;_&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;TrimEnd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;=&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# Generate Key ID (kid) based on SHA-256 fingerprint of the public key coordinates&lt;/span&gt;
&lt;span class="nv"&gt;$sha256&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="no"&gt;[System.Security.Cryptography.SHA256]&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nv"&gt;$fingerprint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$sha256&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ComputeHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$xBytes&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="nv"&gt;$yBytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;$kid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Safe-Base64Url&lt;/span&gt; &lt;span class="nv"&gt;$fingerprint&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;Substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Construct JWK&lt;/span&gt;
&lt;span class="nv"&gt;$jwk&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;@{&lt;/span&gt;
    &lt;span class="n"&gt;kty&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;EC&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;crv&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;P-256&amp;quot;&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Safe-Base64Url&lt;/span&gt; &lt;span class="nv"&gt;$xBytes&lt;/span&gt;
    &lt;span class="n"&gt;y&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Safe-Base64Url&lt;/span&gt; &lt;span class="nv"&gt;$yBytes&lt;/span&gt;
    &lt;span class="n"&gt;kid&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$kid&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;ConvertTo-Json&lt;/span&gt; &lt;span class="n"&gt;-Compress&lt;/span&gt;

&lt;span class="nb"&gt;Write-Output&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;Public JWK:&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;Write-Output&lt;/span&gt; &lt;span class="nv"&gt;$jwk&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To sign the JWT using the TPM key in PowerShell:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="c"&gt;# Load the key from the TPM provider&lt;/span&gt;
&lt;span class="nv"&gt;$cngKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="no"&gt;[System.Security.Cryptography.CngKey]&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$keyName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;$ecdsa&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;New-Object&lt;/span&gt; &lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Security&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Cryptography&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ECDsaCng&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$cngKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Hashing the JWT header and payload&lt;/span&gt;
&lt;span class="nv"&gt;$jwtBytes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="no"&gt;[System.Text.Encoding]&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UTF8&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;$header.$payload&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;$sha256&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="no"&gt;[System.Security.Cryptography.SHA256]&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nv"&gt;$hashBytes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$sha256&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ComputeHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$jwtBytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Sign hash directly (returns raw R || S signature)&lt;/span&gt;
&lt;span class="nv"&gt;$sigBytes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$ecdsa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SignHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$hashBytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;$signature&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Safe-Base64Url&lt;/span&gt; &lt;span class="nv"&gt;$sigBytes&lt;/span&gt;

&lt;span class="nv"&gt;$jwt&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;quot;$header.$payload.$signature&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr /&gt;
&lt;h2 id="4-deployment-and-configuration-guide"&gt;4. Deployment and Configuration Guide&lt;a class="headerlink" href="#4-deployment-and-configuration-guide" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="step-1-initializing-client-configuration"&gt;Step 1: Initializing Client Configuration&lt;a class="headerlink" href="#step-1-initializing-client-configuration" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Create or update your &lt;code&gt;client-swift/config.json&lt;/code&gt; file with your real GCP credentials.&lt;/p&gt;
&lt;h3 id="step-2-compiling-the-swift-client"&gt;Step 2: Compiling the Swift Client&lt;a class="headerlink" href="#step-2-compiling-the-swift-client" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Compile the Swift client on your macOS using the native compiler:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;swiftc&lt;span class="w"&gt; &lt;/span&gt;-O&lt;span class="w"&gt; &lt;/span&gt;client-swift/main.swift&lt;span class="w"&gt; &lt;/span&gt;-o&lt;span class="w"&gt; &lt;/span&gt;client-swift/backup-auth
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;h3 id="step-3-hardware-key-generation-and-registration"&gt;Step 3: Hardware Key Generation and Registration&lt;a class="headerlink" href="#step-3-hardware-key-generation-and-registration" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Execute the binary, pointing it to the &lt;code&gt;terraform/jwks&lt;/code&gt; registration directory to generate the cryptographic private key in the Secure Enclave, display the associated public JWK, and save it directly in the correct format:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;./client-swift/backup-auth&lt;span class="w"&gt; &lt;/span&gt;generate&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--config&lt;span class="w"&gt; &lt;/span&gt;client-swift/config.json&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--key-out&lt;span class="w"&gt; &lt;/span&gt;client-swift/key.wrapped&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;--jwks-dir&lt;span class="w"&gt; &lt;/span&gt;./terraform/jwks
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This command produces:
1. The wrapped physical key token &lt;code&gt;client-swift/key.wrapped&lt;/code&gt; (used for signing JWTs).
2. The individual JWK public key file saved directly in &lt;code&gt;terraform/jwks/&amp;lt;your-machine-name&amp;gt;.json&lt;/code&gt;.
3. The corresponding public JWK displayed on the standard output.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(For Linux servers with TPM 2.0, the equivalent call using the Go client also supports the &lt;code&gt;--jwks-dir&lt;/code&gt; option).&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Repeat this operation for each machine to be registered. Each machine generates its own file in &lt;code&gt;terraform/jwks/&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="step-4-terraform-provisioning"&gt;Step 4: Terraform Provisioning&lt;a class="headerlink" href="#step-4-terraform-provisioning" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Initialize and apply the Terraform configuration. It will automatically compile all machine keys into an aggregated &lt;code&gt;jwks.json&lt;/code&gt;, upload it to GCS along with the OIDC discovery document, configure the WIF pool, and create an IAM binding per machine:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;terraform
terraform&lt;span class="w"&gt; &lt;/span&gt;init
terraform&lt;span class="w"&gt; &lt;/span&gt;apply&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-var&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;project_id=your-gcp-project&amp;quot;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;-var&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;project_number=123456789012&amp;quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;div class="admonition tip"&gt;
&lt;p class="admonition-title"&gt;Quick Addition / Revocation&lt;/p&gt;
&lt;p&gt;To add or revoke a machine later, simply add or delete its &lt;code&gt;.json&lt;/code&gt; file in &lt;code&gt;terraform/jwks/&lt;/code&gt; and rerun &lt;code&gt;terraform apply&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="admonition note"&gt;
&lt;p class="admonition-title"&gt;Towards a GitOps Approach&lt;/p&gt;
&lt;p&gt;In this article, we execute Terraform locally to keep the demonstration simple. In a DevOps workflow, enrolling a new machine would involve a &lt;strong&gt;merge request&lt;/strong&gt; to the Terraform Git repository: the machine&amp;rsquo;s administrator generates their public key, commits it to &lt;code&gt;terraform/jwks/&lt;/code&gt;, and submits an MR. A peer can then &lt;strong&gt;validate the addition&lt;/strong&gt; (verifying identity, fingerprint, etc.) before merging. The CI/CD pipeline then applies the &lt;code&gt;terraform apply&lt;/code&gt; automatically. This GitOps flow provides complete auditability of the authorized machine inventory and can even be fully automated when provisioning new servers.&lt;/p&gt;
&lt;/div&gt;
&lt;hr /&gt;
&lt;h2 id="5-local-integration-choices-agentless-vs-emulator"&gt;5. Local Integration Choices: Agentless vs Emulator&lt;a class="headerlink" href="#5-local-integration-choices-agentless-vs-emulator" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To connect the local authentication of your machines to GCP tools, two options are available:&lt;/p&gt;
&lt;h3 id="option-a-the-recommended-agentless-approach-executable-credentials"&gt;Option A: The Recommended &amp;ldquo;Agentless&amp;rdquo; Approach (Executable Credentials)&lt;a class="headerlink" href="#option-a-the-recommended-agentless-approach-executable-credentials" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This is Google&amp;rsquo;s recommended solution because it aligns with the standard WIF model and avoids opening local network ports in the background.&lt;/p&gt;
&lt;p&gt;The Google SDK runs our local binary directly as an ephemeral subprocess to retrieve the signed JWT and handles exchanging it with Google STS.
You configure a local credentials file (e.g., &lt;code&gt;credentials-config.json&lt;/code&gt;):&lt;/p&gt;
&lt;div class="admonition important"&gt;
&lt;p class="admonition-title"&gt;Adapt Absolute Paths&lt;/p&gt;
&lt;p&gt;Do not forget to adapt the absolute paths (such as &lt;code&gt;/Users/username/...&lt;/code&gt; or &lt;code&gt;/home/username/...&lt;/code&gt;) to the &lt;code&gt;backup-auth&lt;/code&gt; binary, the &lt;code&gt;--config&lt;/code&gt; configuration, and the &lt;code&gt;--key-in&lt;/code&gt; key based on the actual directory structure of your workstation or client server.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;external_account&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;audience&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;//iam.googleapis.com/projects/YOUR_PROJECT_NUMBER/locations/global/workloadIdentityPools/hardware-auth-pool/providers/hardware-oidc-provider&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;subject_token_type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;urn:ietf:params:oauth:token-type:jwt&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;token_url&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://sts.googleapis.com/v1/token&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;service_account_impersonation_url&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/backup-agent-sa@YOUR_PROJECT_ID.iam.gserviceaccount.com:generateAccessToken&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;credential_source&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;executable&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;command&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/Users/username/gcp-workload-identification/client-swift/backup-auth credential --config /Users/username/gcp-workload-identification/client-swift/config.json --key-in /Users/username/gcp-workload-identification/client-swift/key.wrapped&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;timeout_millis&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;output_filesize_limit_bytes&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1048576&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For Windows, you can configure the credential configuration to invoke PowerShell and pass the path to your signing script:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;external_account&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;audience&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;//iam.googleapis.com/projects/YOUR_PROJECT_NUMBER/locations/global/workloadIdentityPools/hardware-auth-pool/providers/hardware-oidc-provider&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;subject_token_type&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;urn:ietf:params:oauth:token-type:jwt&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;token_url&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://sts.googleapis.com/v1/token&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;service_account_impersonation_url&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/backup-agent-sa@YOUR_PROJECT_ID.iam.gserviceaccount.com:generateAccessToken&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;credential_source&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;executable&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;command&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:\\path\\to\\backup-auth.ps1 -GetToken&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;timeout_millis&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;output_filesize_limit_bytes&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1048576&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;To use this file with your applications (via Google client libraries), set the &lt;code&gt;GOOGLE_APPLICATION_CREDENTIALS&lt;/code&gt; environment variable as well as the security variable &lt;code&gt;GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES&lt;/code&gt; which allows running the binary:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;GOOGLE_APPLICATION_CREDENTIALS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;/Users/username/gcp-workload-identification/credentials-config.json&amp;quot;&lt;/span&gt;
&lt;span class="nb"&gt;export&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;For the &lt;code&gt;gcloud&lt;/code&gt; CLI (which does not natively use ADC environment variables for its standard commands), explicitly authenticate the CLI using this file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;gcloud&lt;span class="w"&gt; &lt;/span&gt;auth&lt;span class="w"&gt; &lt;/span&gt;login&lt;span class="w"&gt; &lt;/span&gt;--cred-file&lt;span class="o"&gt;=&lt;/span&gt;/Users/username/gcp-workload-identification/credentials-config.json
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Then, execute your commands by enabling the executable authorization:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;gcloud&lt;span class="w"&gt; &lt;/span&gt;storage&lt;span class="w"&gt; &lt;/span&gt;cp&lt;span class="w"&gt; &lt;/span&gt;/data/backup.tar.gz&lt;span class="w"&gt; &lt;/span&gt;gs://secure-backup-unique-name/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;hr /&gt;
&lt;h3 id="option-b-the-metadata-emulator-local-daemon"&gt;Option B: The Metadata Emulator (Local Daemon)&lt;a class="headerlink" href="#option-b-the-metadata-emulator-local-daemon" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;&lt;em&gt;Fallback Alternative&lt;/em&gt;: If you are using Docker containers or legacy scripts with direct HTTP requests, you can run our Go daemon (&lt;a href="https://gitlab.com/matgou/gcp-workload-identification/-/blob/main/metadata-emulator/main.go"&gt;metadata-emulator/main.go&lt;/a&gt;) in the background on the loopback interface.&lt;/p&gt;
&lt;p&gt;By setting &lt;code&gt;export GCLOUD_METADATA_HOST=127.0.0.1:8080&lt;/code&gt;, network requests are redirected to the emulator, which mimics the behavior of a Google Compute Engine VM.
&lt;em&gt;Note:&lt;/em&gt; &lt;code&gt;GCLOUD_METADATA_HOST&lt;/code&gt; is an internal SDK testing environment variable and should not be considered a long-term production integration point.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="6-scaling-up-gitops-limits"&gt;6. Scaling Up (GitOps &amp;amp; Limits)&lt;a class="headerlink" href="#6-scaling-up-gitops-limits" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="automated-enrollment-gitops"&gt;Automated Enrollment (GitOps)&lt;a class="headerlink" href="#automated-enrollment-gitops" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Across a large fleet of machines, the CI/CD pipeline that provisions on-premises servers can be configured to automatically consolidate public keys:
1. Each new machine generates its key at boot, and the post-provisioning job submits its JWK public key (as a file like &lt;code&gt;jwks/machine-01.json&lt;/code&gt;) via a Pull Request to a Git repository.
2. The CI/CD pipeline (e.g., Terraform) validates the format, merges the individual keys into a global &lt;code&gt;jwks.json&lt;/code&gt; file as described above.
3. The pipeline automatically deploys the updated file to the trusted Cloud Storage bucket.&lt;/p&gt;
&lt;h3 id="scalability-limits-of-a-static-jwks"&gt;Scalability Limits of a Static JWKS&lt;a class="headerlink" href="#scalability-limits-of-a-static-jwks" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Google does not publish strict limits on the size of the JWKS file imported by Google STS. However, a file containing more than 1,000 signing keys (about 400 KB of text data) degrades authentication network performance during token resolution.
For very large fleets, it is best to segment machines into multiple separate JWKS files (one per department or environment) or point the WIF OIDC Provider to a dynamic API (e.g., Cloud Function + Firestore database) that retrieves the public key individually on-demand using the &lt;code&gt;kid&lt;/code&gt; (Key ID) claim.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="7-perspectives-and-sovereignty-spiffe-s3ns"&gt;7. Perspectives and Sovereignty (SPIFFE / S3NS)&lt;a class="headerlink" href="#7-perspectives-and-sovereignty-spiffe-s3ns" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The serverless hardware-based WIF architecture constitutes the first logical step towards adopting standardized cloud-native identity models, such as the CNCF&amp;rsquo;s &lt;strong&gt;SPIFFE/SPIRE&lt;/strong&gt; project.&lt;/p&gt;
&lt;p&gt;In a SPIFFE deployment, local workloads no longer need local wrapped key files; they dynamically retrieve short-lived cryptographic identity tokens (JWT-SVIDs) by querying the SPIFFE Workload API locally via a UNIX domain socket. The central SPIRE server exposes its public JWKS, which is then federated with Google Cloud&amp;rsquo;s WIF service.&lt;/p&gt;
&lt;h3 id="operational-sovereignty-and-trusted-cloud"&gt;Operational Sovereignty and &amp;ldquo;Trusted Cloud&amp;rdquo;&lt;a class="headerlink" href="#operational-sovereignty-and-trusted-cloud" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This architecture integrates naturally with European Trusted Cloud providers such as &lt;strong&gt;S3NS&lt;/strong&gt; (the joint venture between Thales and Google Cloud, qualified SecNumCloud by ANSSI in France).&lt;/p&gt;
&lt;p&gt;In this scenario, the hardware anchor (TPM 2.0) keeps the authentication private key on national soil, within your local datacenters, while the trusted OIDC configuration files are hosted securely and isolated within the S3NS sovereign zone. This ensures a complete separation of powers and robust protection against extraterritorial laws.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;&amp;para;&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For a long time, securing connection from an on-premises machine to cloud providers meant storing a static Service Account key on the local disk, carrying a persistent risk of leakage. With Workload Identity Federation and modern hardware security modules (TPM 2.0 and Secure Enclave), it is now possible to completely eliminate these static secrets. By leveraging the native Executable Credentials feature, this approach brings hybrid infrastructures closer to Zero Trust principles, while paving the way for universal identity architectures like SPIFFE/SPIRE.&lt;/p&gt;</content><category term="en"/><category term='["gcp"'/><category term='"security"'/><category term='"wif"'/><category term='"tpm"'/><category term='"secure-enclave"'/><category term='"zero-trust"]'/></entry></feed>