From 7219e2746d1c0f47e15867e5f7b3bdad99817706 Mon Sep 17 00:00:00 2001 From: Michael Crumm Date: Fri, 20 Sep 2024 15:19:04 -0700 Subject: [PATCH 1/2] Support direct workload identity federation --- lib/goth/token.ex | 3 +-- ...-credentials-direct-workload-identity.json | 12 ++++++++++ test/goth/token_test.exs | 24 +++++++++++++++++++ 3 files changed, 37 insertions(+), 2 deletions(-) create mode 100644 test/data/test-credentials-direct-workload-identity.json diff --git a/lib/goth/token.ex b/lib/goth/token.ex index ad708ae..b2e3019 100644 --- a/lib/goth/token.ex +++ b/lib/goth/token.ex @@ -402,9 +402,8 @@ defmodule Goth.Token do defp handle_workload_identity_response( {:ok, %{status: 200, body: body}}, - %{source: {:workload_identity, credentials}} = config + %{source: {:workload_identity, %{"service_account_impersonation_url" => url}}} = config ) do - url = Map.get(credentials, "service_account_impersonation_url") %{"access_token" => token, "token_type" => type} = Jason.decode!(body) headers = [{"content-type", "text/json"}, {"Authorization", "#{type} #{token}"}] diff --git a/test/data/test-credentials-direct-workload-identity.json b/test/data/test-credentials-direct-workload-identity.json new file mode 100644 index 0000000..7a40ec6 --- /dev/null +++ b/test/data/test-credentials-direct-workload-identity.json @@ -0,0 +1,12 @@ +{ + "audience": "//iam.googleapis.com/projects/my-project/locations/global/workloadIdentityPools/my-cluster/providers/my-provider", + "credential_source": { + "file": "test/data/workload-identity-token", + "format": { + "type": "text" + } + }, + "subject_token_type": "urn:ietf:params:oauth:token-type:jwt", + "token_url": "https://sts.googleapis.com/v1/token", + "type": "external_account" +} diff --git a/test/goth/token_test.exs b/test/goth/token_test.exs index 9df242a..1a90f3b 100644 --- a/test/goth/token_test.exs +++ b/test/goth/token_test.exs @@ -228,6 +228,30 @@ defmodule Goth.TokenTest do assert token.scope == nil end + test "fetch/1 from direct workload identity" do + token_bypass = Bypass.open() + + Bypass.expect(token_bypass, fn conn -> + assert conn.request_path == "/v1/token" + + body = ~s|{"access_token":"dummy","expires_in":599,"token_type":"Bearer"}| + Plug.Conn.resp(conn, 200, body) + end) + + credentials = + File.read!("test/data/test-credentials-direct-workload-identity.json") + |> Jason.decode!() + |> Map.put("token_url", "http://localhost:#{token_bypass.port}/v1/token") + + config = %{ + source: {:workload_identity, credentials} + } + + {:ok, token} = Goth.Token.fetch(config) + assert token.token == "dummy" + assert token.scope == nil + end + defp random_service_account_credentials do %{ "private_key" => random_private_key(), From 2a3043841cce5ac80b9dbc3546e9cad61be3e11e Mon Sep 17 00:00:00 2001 From: Michael Crumm Date: Fri, 20 Sep 2024 15:30:01 -0700 Subject: [PATCH 2/2] support default and json formats for credential_source --- lib/goth/token.ex | 15 +++++++++++- ...entials-direct-workload-identity-json.json | 13 ++++++++++ ...-credentials-direct-workload-identity.json | 5 +--- test/data/workload-identity-token.json | 3 +++ test/goth/token_test.exs | 24 +++++++++++++++++++ 5 files changed, 55 insertions(+), 5 deletions(-) create mode 100644 test/data/test-credentials-direct-workload-identity-json.json create mode 100644 test/data/workload-identity-token.json diff --git a/lib/goth/token.ex b/lib/goth/token.ex index b2e3019..01171cd 100644 --- a/lib/goth/token.ex +++ b/lib/goth/token.ex @@ -390,7 +390,20 @@ defmodule Goth.Token do {url, audience} end - defp subject_token_from_credential_source(%{"file" => file, "format" => %{"type" => "text"}}) do + defp subject_token_from_credential_source(%{"file" => file, "format" => format}) do + binary = File.read!(file) + + case format do + %{"type" => "text"} -> + binary + + %{"type" => "json", "subject_token_field_name" => field} -> + binary |> Jason.decode!() |> Map.fetch!(field) + end + end + + # the default file type if not specified is "text" + defp subject_token_from_credential_source(%{"file" => file}) do File.read!(file) end diff --git a/test/data/test-credentials-direct-workload-identity-json.json b/test/data/test-credentials-direct-workload-identity-json.json new file mode 100644 index 0000000..a9bf89e --- /dev/null +++ b/test/data/test-credentials-direct-workload-identity-json.json @@ -0,0 +1,13 @@ +{ + "audience": "//iam.googleapis.com/projects/my-project/locations/global/workloadIdentityPools/my-cluster/providers/my-provider", + "credential_source": { + "file": "test/data/workload-identity-token.json", + "format": { + "type": "json", + "subject_token_field_name": "token" + } + }, + "subject_token_type": "urn:ietf:params:oauth:token-type:jwt", + "token_url": "https://sts.googleapis.com/v1/token", + "type": "external_account" +} diff --git a/test/data/test-credentials-direct-workload-identity.json b/test/data/test-credentials-direct-workload-identity.json index 7a40ec6..d65ede4 100644 --- a/test/data/test-credentials-direct-workload-identity.json +++ b/test/data/test-credentials-direct-workload-identity.json @@ -1,10 +1,7 @@ { "audience": "//iam.googleapis.com/projects/my-project/locations/global/workloadIdentityPools/my-cluster/providers/my-provider", "credential_source": { - "file": "test/data/workload-identity-token", - "format": { - "type": "text" - } + "file": "test/data/workload-identity-token" }, "subject_token_type": "urn:ietf:params:oauth:token-type:jwt", "token_url": "https://sts.googleapis.com/v1/token", diff --git a/test/data/workload-identity-token.json b/test/data/workload-identity-token.json new file mode 100644 index 0000000..892147d --- /dev/null +++ b/test/data/workload-identity-token.json @@ -0,0 +1,3 @@ +{ + "token": "workload-identity-token" +} diff --git a/test/goth/token_test.exs b/test/goth/token_test.exs index 1a90f3b..76601a9 100644 --- a/test/goth/token_test.exs +++ b/test/goth/token_test.exs @@ -252,6 +252,30 @@ defmodule Goth.TokenTest do assert token.scope == nil end + test "fetch/1 from direct workload identity, json format" do + token_bypass = Bypass.open() + + Bypass.expect(token_bypass, fn conn -> + assert conn.request_path == "/v1/token" + + body = ~s|{"access_token":"dummy","expires_in":599,"token_type":"Bearer"}| + Plug.Conn.resp(conn, 200, body) + end) + + credentials = + File.read!("test/data/test-credentials-direct-workload-identity-json.json") + |> Jason.decode!() + |> Map.put("token_url", "http://localhost:#{token_bypass.port}/v1/token") + + config = %{ + source: {:workload_identity, credentials} + } + + {:ok, token} = Goth.Token.fetch(config) + assert token.token == "dummy" + assert token.scope == nil + end + defp random_service_account_credentials do %{ "private_key" => random_private_key(),