Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

idtoken: support authorized_user credential type #873

Open
bouk opened this issue Feb 12, 2021 · 20 comments
Open

idtoken: support authorized_user credential type #873

bouk opened this issue Feb 12, 2021 · 20 comments
Assignees
Labels
type: feature request ‘Nice-to-have’ improvement, new feature or different behavior or design.

Comments

@bouk
Copy link

bouk commented Feb 12, 2021

Right now there's no way to get an ID token if your credentials are user credentials e.g. if you're signed into gcloud locally.

Right now I have to exec out into gcloud auth print-identity-token to get a token, it would be great if this library supported it natively.

@yoshi-automation yoshi-automation added the triage me I really want to be triaged. label Feb 13, 2021
@bouk
Copy link
Author

bouk commented Feb 16, 2021

I managed to work around this, as follows:

import (
	"context"
	"fmt"

	"golang.org/x/oauth2"
	"golang.org/x/oauth2/google"
	"google.golang.org/api/idtoken"
)

func IDTokenTokenSource(ctx context.Context, audience string) (oauth2.TokenSource, error) {
	// First we try the idtoken package, which only works for service accounts
	ts, err := idtoken.NewTokenSource(ctx, audience)
	if err != nil {
		if err.Error() != `idtoken: credential must be service_account, found "authorized_user"` {
			return nil, err
		}
		// If that fails, we use our Application Default Credentials to fetch an id_token on the fly
		gts, err := google.DefaultTokenSource(ctx)
		if err != nil {
			return nil, err
		}
		ts = oauth2.ReuseTokenSource(nil, &idTokenSource{TokenSource: gts})
	}
	return ts, nil
}

// idTokenSource is an oauth2.TokenSource that wraps another
// It takes the id_token from TokenSource and passes that on as a bearer token
type idTokenSource struct {
	TokenSource oauth2.TokenSource
}

func (s *idTokenSource) Token() (*oauth2.Token, error) {
	token, err := s.TokenSource.Token()
	if err != nil {
		return nil, err
	}

	idToken, ok := token.Extra("id_token").(string)
	if !ok {
		return nil, fmt.Errorf("token did not contain an id_token")
	}

	return &oauth2.Token{
		AccessToken: idToken,
		TokenType:   "Bearer",
		Expiry:      token.Expiry,
	}, nil
}

This code will wrap the google oauth TokenSource to pass through its id_token

@codyoss
Copy link
Member

codyoss commented Feb 16, 2021

Hey @bouk you are correct that we don't currently support this, but this was done intentionally at the time of writing. The identity token spec we follow states this is optional: AIP 4116. That being said I am curious if the other language libraries we support currently have this feature. @bshaffer do you know if user credentials are used in other languages?

@codyoss codyoss added type: feature request ‘Nice-to-have’ improvement, new feature or different behavior or design. and removed triage me I really want to be triaged. labels Feb 16, 2021
@bouk
Copy link
Author

bouk commented Feb 16, 2021

The python SDK seems to support it at least, since that's what gcloud uses under the hood (I assume?)

@codyoss
Copy link
Member

codyoss commented Feb 16, 2021

@bouk It looks like the python auth library only works with service accounts as well. I am not against this request, but I would prefer to be consistent with what our other libraries in other languages are doing.

@j0hnsmith
Copy link

j0hnsmith commented Jan 12, 2022

If service accounts can be impersonated it follows that anything that requires a service account should also work with an impersonated service account and the platform has decreed that impersonating a service account is a thing...

@codyoss being consistent is great but in this case it would require all the libraries in all the languages be updated at the same time, virtually impossible, some library has to be first.

Very keen to see this feature added.

Edit: thought this issue was that impersonated_service_account wasn't supported.

@liufuyang
Copy link

Any update on this? Would be nice if we don't have to manually do the workaround mentioned above :) @codyoss

@tdi
Copy link

tdi commented Sep 30, 2022

At least it would be great to have suport for impersonated service accounts to test locally.

idtoken: credential must be service_account, found "impersonated_service_account"

@liufuyang
Copy link

liufuyang commented Sep 30, 2022

I hope there is not a "deadlock" in pushing request changes like this, as what we were saying above is that "python lib does that logic, so we gonna do it the same here". I am not sure but I hope it won't be like some python lib users encountered the same and they created some issue (or maybe not as less people use python to work with google-apis?) saying to change this and they got a reply saying "golang package does that so we cannot change". :)

@tdi
Copy link

tdi commented Sep 30, 2022

@codyoss would you be open in pull request for at least accepting impersonated_service_account? I think not supporting authorized_user makes sense but impersonated_service_account is a valid ask.

@codyoss
Copy link
Member

codyoss commented Sep 30, 2022

@tdi google.FindDefaultCredentials already supports that type of file out of the box.

See code: impersonatedServiceAccount

@liufuyang
Copy link

ts, err := idtoken.NewTokenSource(ctx, audience)

@codyoss Would this line of code in the above example work when an impersonated_service_account is used?

The idtoken is from "google.golang.org/api/idtoken"

Thank you :)

@liufuyang
Copy link

@codyoss I think it is this line creating problems here

if f.Type != "service_account" {

This blocks impersonated_service_account and authorized_user to be used.

I know you mentioned that on the Python package it is the same logic

But I think perhaps all those packages should update their rules to have impersonated_service_account allowed to pass as well? (Perhaps the change can be done from the golang-packages side?)

@liufuyang
Copy link

Another question, reading this https://google.aip.dev/auth/4116
It says:

When a target audience is provided by the developer, ADC should fetch an ID token with the credentials provided. For example, if service account keys are provided, ADC will request an ID token from the OAuth token endpoint.

But the page seems didn't mention that impersonated_service_account and authorized_user should be blocked? Why don't allow them to follow the same logic as service_account? :)

@codyoss
Copy link
Member

codyoss commented Oct 20, 2022

This package predates impersonated_service_account, so that is why it has not been considered. Maybe enhancements should be made to support that type. I will try to look at this next week to see if there are any technical blockers for this or if it could proceed.

@liufuyang
Copy link

Ah, that is very nice of you, thank you very much 😃

@liufuyang
Copy link

@codyoss I believe to enable impersonated_service_account, this issue golang/oauth2#600 on the auth2 package side will be needed to be solved first or? Thanks :)

@GusPrice
Copy link

Having some level of support here for a service account or service account impersonation would be awesome for the developer experience. Mostly just commenting here to register a bit more demand and follow any outcome :D

@codyoss
Copy link
Member

codyoss commented Nov 18, 2022

@liufuyang I think this package could support impersonated_service_account after taking a closer look, but might have to do so by reincorporating some of the logic in the oauth2 package here. Glanced at the issue you filed. The reason for all of that is the way the impersonated service accounts authenticate is quite different than a normal flow. They actually are just calling the https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateAccessTokenunder the hood.

This package could do something similar and have a wrapper around calling https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateIdToken. So although it is not as convenient today this can be achieved by calling that API to create a TokenSource. We do have a helper for doing this today with https://pkg.go.dev/google.golang.org/api/impersonate#IDTokenSource, but it does not work auto-magically with impersonated_service_account .

@liufuyang
Copy link

liufuyang commented Feb 8, 2023

@codyoss @adrianajg Thank you and I saw some nice code has been merged but has this been correctly implemented?

I tested with google.golang.org/api v0.109.0 and this is what I see if I use an impersonated account: the idToken field has empty value.
image

While using a normal service account's json key file, this is what I see:

There is an idToken field in raw containing value.
image


I glanced the code a bit while debugging, perhaps we need to set the id_token in the raw field here as well?

return &oauth2.Token{
AccessToken: generateIDTokenResp.Token,
// Generated ID tokens are good for one hour.
Expiry: now.Add(1 * time.Hour),
}, nil

Or, do we supposed to just use that AccessToken field on the Token struct and never use that Extra() method?

Thank you :)

@brachipa
Copy link

Impersonated service account still unsupported:

return nil, fmt.Errorf("idtoken: option.WithImpersonatedCredentials not supported")

Also creating the option via option.ImpersonateCredentials is deprecated and in the docs it is written that this is should be done from different package and also WithTokenSource option:

// instead with the WithTokenSource option.

which is also unsupported:
https://github.com/googleapis/google-api-go-client/blob/b1c45c2251d9c3d524d7ca0170d5becfda31b1d5/idtoken/idtoken.go#L92C8-L92C19

Also regarding this workaround #873 (comment) , I can't use it when working with IAP service, we have to send a different audience and the ts = oauth2.ReuseTokenSource(nil, &idTokenSource{TokenSource: gts}) is using the default audience.

The only thing that works is to download some service account save it a file and run with it locally. which isn't ideal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: feature request ‘Nice-to-have’ improvement, new feature or different behavior or design.
Projects
None yet
Development

No branches or pull requests

8 participants