forked from Mirrors/oauth2
google: add support for "impersonated_service_account" credential type.
New credential type supported: "impersonated_service_account".
Extend the "credentialsFile" struct to take into account the credential source for the impersonation.
Reuse of `ImpersonateTokenSource` struct, from `google/internal/externalaccount/Impersonate.go' file. The struct has a package-scope visibility now.
Fixes: #515
Change-Id: I87e213be6d4b6add2d6d82b91b1b38e43a0d2fe4
GitHub-Last-Rev: 14806e6b37
GitHub-Pull-Request: golang/oauth2#516
Reviewed-on: https://go-review.googlesource.com/c/oauth2/+/344369
Reviewed-by: Cody Oss <codyoss@google.com>
Trust: Cody Oss <codyoss@google.com>
Trust: Michael Knyszek <mknyszek@google.com>
Run-TryBot: Cody Oss <codyoss@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
This commit is contained in:
parent
6b3c2da341
commit
ba495a64dc
|
@ -95,6 +95,7 @@ const (
|
||||||
serviceAccountKey = "service_account"
|
serviceAccountKey = "service_account"
|
||||||
userCredentialsKey = "authorized_user"
|
userCredentialsKey = "authorized_user"
|
||||||
externalAccountKey = "external_account"
|
externalAccountKey = "external_account"
|
||||||
|
impersonatedServiceAccount = "impersonated_service_account"
|
||||||
)
|
)
|
||||||
|
|
||||||
// credentialsFile is the unmarshalled representation of a credentials file.
|
// credentialsFile is the unmarshalled representation of a credentials file.
|
||||||
|
@ -121,9 +122,13 @@ type credentialsFile struct {
|
||||||
TokenURLExternal string `json:"token_url"`
|
TokenURLExternal string `json:"token_url"`
|
||||||
TokenInfoURL string `json:"token_info_url"`
|
TokenInfoURL string `json:"token_info_url"`
|
||||||
ServiceAccountImpersonationURL string `json:"service_account_impersonation_url"`
|
ServiceAccountImpersonationURL string `json:"service_account_impersonation_url"`
|
||||||
|
Delegates []string `json:"delegates"`
|
||||||
CredentialSource externalaccount.CredentialSource `json:"credential_source"`
|
CredentialSource externalaccount.CredentialSource `json:"credential_source"`
|
||||||
QuotaProjectID string `json:"quota_project_id"`
|
QuotaProjectID string `json:"quota_project_id"`
|
||||||
WorkforcePoolUserProject string `json:"workforce_pool_user_project"`
|
WorkforcePoolUserProject string `json:"workforce_pool_user_project"`
|
||||||
|
|
||||||
|
// Service account impersonation
|
||||||
|
SourceCredentials *credentialsFile `json:"source_credentials"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *credentialsFile) jwtConfig(scopes []string, subject string) *jwt.Config {
|
func (f *credentialsFile) jwtConfig(scopes []string, subject string) *jwt.Config {
|
||||||
|
@ -180,6 +185,23 @@ func (f *credentialsFile) tokenSource(ctx context.Context, params CredentialsPar
|
||||||
WorkforcePoolUserProject: f.WorkforcePoolUserProject,
|
WorkforcePoolUserProject: f.WorkforcePoolUserProject,
|
||||||
}
|
}
|
||||||
return cfg.TokenSource(ctx)
|
return cfg.TokenSource(ctx)
|
||||||
|
case impersonatedServiceAccount:
|
||||||
|
if f.ServiceAccountImpersonationURL == "" || f.SourceCredentials == nil {
|
||||||
|
return nil, errors.New("missing 'source_credentials' field or 'service_account_impersonation_url' in credentials")
|
||||||
|
}
|
||||||
|
|
||||||
|
ts, err := f.SourceCredentials.tokenSource(ctx, params)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
imp := externalaccount.ImpersonateTokenSource{
|
||||||
|
Ctx: ctx,
|
||||||
|
URL: f.ServiceAccountImpersonationURL,
|
||||||
|
Scopes: params.Scopes,
|
||||||
|
Ts: ts,
|
||||||
|
Delegates: f.Delegates,
|
||||||
|
}
|
||||||
|
return oauth2.ReuseTokenSource(nil, imp), nil
|
||||||
case "":
|
case "":
|
||||||
return nil, errors.New("missing 'type' field in credentials")
|
return nil, errors.New("missing 'type' field in credentials")
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -140,11 +140,11 @@ func (c *Config) tokenSource(ctx context.Context, tokenURLValidPats []*regexp.Re
|
||||||
}
|
}
|
||||||
scopes := c.Scopes
|
scopes := c.Scopes
|
||||||
ts.conf.Scopes = []string{"https://www.googleapis.com/auth/cloud-platform"}
|
ts.conf.Scopes = []string{"https://www.googleapis.com/auth/cloud-platform"}
|
||||||
imp := impersonateTokenSource{
|
imp := ImpersonateTokenSource{
|
||||||
ctx: ctx,
|
Ctx: ctx,
|
||||||
url: c.ServiceAccountImpersonationURL,
|
URL: c.ServiceAccountImpersonationURL,
|
||||||
scopes: scopes,
|
Scopes: scopes,
|
||||||
ts: oauth2.ReuseTokenSource(nil, ts),
|
Ts: oauth2.ReuseTokenSource(nil, ts),
|
||||||
}
|
}
|
||||||
return oauth2.ReuseTokenSource(nil, imp), nil
|
return oauth2.ReuseTokenSource(nil, imp), nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,30 +29,44 @@ type impersonateTokenResponse struct {
|
||||||
ExpireTime string `json:"expireTime"`
|
ExpireTime string `json:"expireTime"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type impersonateTokenSource struct {
|
// ImpersonateTokenSource uses a source credential, stored in Ts, to request an access token to the provided URL.
|
||||||
ctx context.Context
|
// Scopes can be defined when the access token is requested.
|
||||||
ts oauth2.TokenSource
|
type ImpersonateTokenSource struct {
|
||||||
|
// Ctx is the execution context of the impersonation process
|
||||||
|
// used to perform http call to the URL. Required
|
||||||
|
Ctx context.Context
|
||||||
|
// Ts is the source credential used to generate a token on the
|
||||||
|
// impersonated service account. Required.
|
||||||
|
Ts oauth2.TokenSource
|
||||||
|
|
||||||
url string
|
// URL is the endpoint to call to generate a token
|
||||||
scopes []string
|
// on behalf the service account. Required.
|
||||||
|
URL string
|
||||||
|
// Scopes that the impersonated credential should have. Required.
|
||||||
|
Scopes []string
|
||||||
|
// Delegates are the service account email addresses in a delegation chain.
|
||||||
|
// Each service account must be granted roles/iam.serviceAccountTokenCreator
|
||||||
|
// on the next service account in the chain. Optional.
|
||||||
|
Delegates []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Token performs the exchange to get a temporary service account token to allow access to GCP.
|
// Token performs the exchange to get a temporary service account token to allow access to GCP.
|
||||||
func (its impersonateTokenSource) Token() (*oauth2.Token, error) {
|
func (its ImpersonateTokenSource) Token() (*oauth2.Token, error) {
|
||||||
reqBody := generateAccessTokenReq{
|
reqBody := generateAccessTokenReq{
|
||||||
Lifetime: "3600s",
|
Lifetime: "3600s",
|
||||||
Scope: its.scopes,
|
Scope: its.Scopes,
|
||||||
|
Delegates: its.Delegates,
|
||||||
}
|
}
|
||||||
b, err := json.Marshal(reqBody)
|
b, err := json.Marshal(reqBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("oauth2/google: unable to marshal request: %v", err)
|
return nil, fmt.Errorf("oauth2/google: unable to marshal request: %v", err)
|
||||||
}
|
}
|
||||||
client := oauth2.NewClient(its.ctx, its.ts)
|
client := oauth2.NewClient(its.Ctx, its.Ts)
|
||||||
req, err := http.NewRequest("POST", its.url, bytes.NewReader(b))
|
req, err := http.NewRequest("POST", its.URL, bytes.NewReader(b))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("oauth2/google: unable to create impersonation request: %v", err)
|
return nil, fmt.Errorf("oauth2/google: unable to create impersonation request: %v", err)
|
||||||
}
|
}
|
||||||
req = req.WithContext(its.ctx)
|
req = req.WithContext(its.Ctx)
|
||||||
req.Header.Set("Content-Type", "application/json")
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
|
|
Loading…
Reference in New Issue