peburrows / goth

Elixir package for Oauth authentication via Google Cloud APIs
http://hexdocs.pm/goth
MIT License
284 stars 108 forks source link

Using several service accounts #35

Closed apognu closed 3 years ago

apognu commented 6 years ago

Is there a way to use different service account (possibly related to completely different projects)?

In the context of my current project, I need to call the Google APIs as a dozen different users in order to retrieve data from all my projects. Does the feature already exist?

Thanks.

peburrows commented 6 years ago

Currently, Goth doesn't support multiple accounts, unfortunately. I definitely think it's worth adding, and would be happy to accept an PR that adds that functionality.

I'll take a look at implementing this when I get a chance.

peburrows commented 6 years ago

I should mention that Goth does, however, allow you to pass in "the email address of the user for which the application is requesting delegated access" as a second argument to certain functions like Token.for_scope/2 (this optional email address will be added as a sub claim on the JWT that is generated when requesting a token from the API). This will allow your application to make authenticated requests on behalf of another user:

{:ok, token} = 
  Goth.Token.for_scope(
    "https://www.googleapis.com/auth/pubsub", 
    "delegating-user@example.com"
  )

See https://developers.google.com/identity/protocols/OAuth2ServiceAccount#additionalclaims for details on how the sub claim works.

apognu commented 6 years ago

The argument for Token.for_scope/2 serves its own purpose, but as I understand it, it is used to impersonate a user for the same project. What I am looking for a a way to parse, store and use credentials from projects that might have nothing to do with each other.

I might find some time to craft a PR around this, but my guess is this would change the anatomy of the project quite a lot, so it may warrant some discussion beforehand.

The way I imagined it would be using a credentials file in the form of a JSON array instead of plain maps in that particular case. Something like this:

[
  {
    "client_email": "xxx@project.iam.gserviceaccount.com",
    ...
  },
  {
    "client_email", "yyy@project.iam.gserviceaccount.com",
    ...
  }
]

All internals would then allow for a new argument, which would specify which service account it targets, while still keeping compatibility with regular-shape credentials file transparently (some kind of default for when only one service account is specified).

This would still allow the sub parametter, to be able to impersonate a user from each of the service account.

What do you think?

apognu commented 6 years ago

I have something that works, I'll submit a PR when I get home tonight.

parkerjm commented 6 years ago

I would also love this functionality. Let me know if I can assist with the PR.

apognu commented 6 years ago

The PR I submitted should cover it. Waiting for merge or comment.

exAspArk commented 6 years ago

From reading the current source code, looks like it should be possible with:

# config.ex
config :goth, json: "[]"

# client.ex
config = "new.json" |> File.read!() |> Poison.decode!()
Goth.Config.add_config(config)
{:ok, token} = Goth.Token.for_scope({ config["client_email"], "https://www.googleapis.com/auth/..." })
xingxing commented 5 years ago

Hey, guys. Allow me to introduce you a new repo that I created to resolve this issue, Gotham

we can put current service account into process dictionary, thus make our APIs invoking more naturally.

Welcome for any criticism and PR, any time.

peburrows commented 5 years ago

Honestly, I believe this issue is actually resolved by #41 and should probably be closed.

@xingxing can you help me understand what problem Gotham solves that #41 doesn't?

xingxing commented 5 years ago

Honestly, I believe this issue is actually resolved by #41 and should probably be closed.

@xingxing can you help me understand what problem Gotham solves that #41 doesn't?

Why I think #41 is not good enough

Hi @peburrows, #41 requires to add at least one parameter/option (sub) to every method you need applying multiple GCP service accounts.

That seems OK for this single library, but what if you want to make some library where used Goth (e.g. kane) to support multiple service accounts, that will be come to a nightmare. I found so many option/sub-sentence need to add, actually, I have tried to deal with it (peburrows/kane#27) and given up when I thought we can do it better.

Why Gotham is better

  1. Gotham uses a dynamic supervisor to boot a set of config and token store for each service account, thus you can add/remove a service account at runtime.
  2. Inspired by elixir-lang/gettext, Gotham uses process dictionary to store service account name that you want to use in current process. You don't even have to explicitly declare a current service account. Gotham will try to use the default one that is declared in application config.
  3. Use semantically independent account name (e.g. :withdrawal_publisher) instead of “confusing@project.iam.gserviceaccount.com”

Usage looks like:

config :gotham,
  default_account: :account1,
  accounts: [
    {:account1, file_path: "path/to/google/json/creds.json"},
    {:account2, file_path: "path/to/google/json/creds.json"}
  ]
# Use default account
{:ok, token} = Gotham.for_scope("https://www.googleapis.com/auth/pubsub")

%Gotham.Token{
  project_id: "test",
  access_token: "TOKEN",
  account_name: :account1,
  expire_at: 1561090622,
  scope: "https://www.googleapis.com/auth/pubsub",
  token_type: "Bearer"
}

# Change acocunt

Gotham.put_account_name(:account2)
Gotham.for_scope("https://www.googleapis.com/auth/pubsub")

{:ok,
 %Gotham.Token{
   project_id: "test",
   access_token: "TOKEN",
   account_name: :account2,
   expire_at: 1561092261,
   scope: "https://www.googleapis.com/auth/pubsub",
   token_type: "Bearer"
 }}

# Run a function with a specific account

Gotham.with_account_name(:account2, fn ->
  Gotham.for_scope("https://www.googleapis.com/auth/pubsub")
end)

{:ok,
 %Gotham.Token{
   project_id: "test",
   access_token: "TOKEN",
   account_name: :account2,
   expire_at: 1561092261,
   scope: "https://www.googleapis.com/auth/pubsub",
   token_type: "Bearer"
 }}
  1. Support multiple service accounts a piece of cake for library that used Gotham. You don't even have to change any code. Compare peburrows/kane#29 and peburrows/kane#27 .
pankaj-ag commented 3 years ago

Hi @all,

Any update on this one. I need to load the account info dynamically. Please Let me know if somebody found and any solution or workaround for this.

xingxing commented 3 years ago

Hi @ALL,

Any update on this one. I need to load the account info dynamically. Please Let me know if somebody found and any solution or workaround for this.

Let's check my repo https://github.com/xingxing/gotham, hopefully it can solve your requirement.

pankaj-ag commented 3 years ago

That's my solution in case that helps.

https://github.com/glific/glific/commit/a70f41f778eac8f440eb27085d04e56398d22367#diff-2983f2b7dd4791bffad2d17e6bd591b857afea1724e45372a2d681ef21f18443

wojtekmach commented 3 years ago

Closing in favour or #82.