10 Commits

Author SHA1 Message Date
452d0a63bf remove usage of appengine to get rid of unsafe imports 2024-02-08 19:41:56 +01:00
Gopher Robot
ebe81ad837 go.mod: update golang.org/x dependencies
Update golang.org/x dependencies to their latest tagged versions.

Change-Id: I8228a126b322fb14250bbb5933199ce45e8584d3
Reviewed-on: https://go-review.googlesource.com/c/oauth2/+/562496
Reviewed-by: Than McIntosh <thanm@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Gopher Robot <gobot@golang.org>
2024-02-08 13:19:31 +00:00
Chris Smith
adffd94437 google/internal/externalaccount: update serviceAccountImpersonationRE to support universe domain
Change-Id: Iafe35c293209bd88997c876341ebde7ac9ecda93
Reviewed-on: https://go-review.googlesource.com/c/oauth2/+/557195
TryBot-Bypass: Cody Oss <codyoss@google.com>
Reviewed-by: Cody Oss <codyoss@google.com>
Auto-Submit: Cody Oss <codyoss@google.com>
2024-01-19 20:50:34 +00:00
Chris Smith
deefa7e836 google/downscope: add DownscopingConfig.UniverseDomain to support TPC
Change-Id: I3669352b382414ea640ca176afa4071995fc5ff1
Reviewed-on: https://go-review.googlesource.com/c/oauth2/+/557135
Reviewed-by: Cody Oss <codyoss@google.com>
TryBot-Bypass: Cody Oss <codyoss@google.com>
Auto-Submit: Cody Oss <codyoss@google.com>
2024-01-19 18:57:04 +00:00
Gopher Robot
39adbb7807 go.mod: update golang.org/x dependencies
Update golang.org/x dependencies to their latest tagged versions.

Change-Id: Icf68cb33585a13df206afacdb79832ea76f82346
Reviewed-on: https://go-review.googlesource.com/c/oauth2/+/554676
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Than McIntosh <thanm@google.com>
Auto-Submit: Gopher Robot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
2024-01-08 18:34:15 +00:00
Chris Smith
4ce7bbb2ff google: add Credentials.GetUniverseDomain with GCE MDS support
* Deprecate Credentials.UniverseDomain

Change-Id: I1cbc842fbfce35540c8dff99fec09e036b9e2cdf
Reviewed-on: https://go-review.googlesource.com/c/oauth2/+/554215
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Cody Oss <codyoss@google.com>
Auto-Submit: Cody Oss <codyoss@google.com>
Reviewed-by: Cody Oss <codyoss@google.com>
Reviewed-by: Viacheslav Rostovtsev <virost@google.com>
2024-01-05 14:38:43 +00:00
Chris Smith
1e6999b1be google: add UniverseDomain to CredentialsParams
Change-Id: I7925b8341e1f047d0115acd7a01a34679a489ee0
Reviewed-on: https://go-review.googlesource.com/c/oauth2/+/552716
Reviewed-by: Cody Oss <codyoss@google.com>
Run-TryBot: Cody Oss <codyoss@google.com>
Reviewed-by: Viacheslav Rostovtsev <virost@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2024-01-04 15:11:51 +00:00
Gopher Robot
6e9ec9323d go.mod: update golang.org/x dependencies
Update golang.org/x dependencies to their latest tagged versions.

Change-Id: Iad79e50dacd89c4cd0a40d966a1a7ba4cdc3d1a4
Reviewed-on: https://go-review.googlesource.com/c/oauth2/+/545176
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
Auto-Submit: Gopher Robot <gobot@golang.org>
2023-11-27 17:50:56 +00:00
Gopher Robot
e067960af8 go.mod: update golang.org/x dependencies
Update golang.org/x dependencies to their latest tagged versions.

Change-Id: Id1413f67816220ef8039fb933088f4b7f50d70e5
Reviewed-on: https://go-review.googlesource.com/c/oauth2/+/540817
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Auto-Submit: Gopher Robot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
2023-11-08 20:28:19 +00:00
Leo
4c91c17b32 google: adds header to security considerations section
Change-Id: I29b93715876f233ae52687c8223fd8733a2a3b80
GitHub-Last-Rev: f15c4cf1a5
GitHub-Pull-Request: golang/oauth2#677
Reviewed-on: https://go-review.googlesource.com/c/oauth2/+/535895
Reviewed-by: Cody Oss <codyoss@google.com>
Run-TryBot: Cody Oss <codyoss@google.com>
Reviewed-by: Alex Eitzman <eitzman@google.com>
Auto-Submit: Cody Oss <codyoss@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
2023-10-17 20:42:42 +00:00
15 changed files with 359 additions and 211 deletions

8
go.mod
View File

@@ -5,12 +5,6 @@ go 1.18
require ( require (
cloud.google.com/go/compute/metadata v0.2.3 cloud.google.com/go/compute/metadata v0.2.3
github.com/google/go-cmp v0.5.9 github.com/google/go-cmp v0.5.9
google.golang.org/appengine v1.6.7
) )
require ( require cloud.google.com/go/compute v1.20.1 // indirect
cloud.google.com/go/compute v1.20.1 // indirect
github.com/golang/protobuf v1.5.3 // indirect
golang.org/x/net v0.16.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
)

20
go.sum
View File

@@ -2,25 +2,5 @@ cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZN
cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM= cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos=
golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=

View File

@@ -1,38 +0,0 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package google
import (
"context"
"time"
"golang.org/x/oauth2"
)
// Set at init time by appengine_gen1.go. If nil, we're not on App Engine standard first generation (<= Go 1.9) or App Engine flexible.
var appengineTokenFunc func(c context.Context, scopes ...string) (token string, expiry time.Time, err error)
// Set at init time by appengine_gen1.go. If nil, we're not on App Engine standard first generation (<= Go 1.9) or App Engine flexible.
var appengineAppIDFunc func(c context.Context) string
// AppEngineTokenSource returns a token source that fetches tokens from either
// the current application's service account or from the metadata server,
// depending on the App Engine environment. See below for environment-specific
// details. If you are implementing a 3-legged OAuth 2.0 flow on App Engine that
// involves user accounts, see oauth2.Config instead.
//
// First generation App Engine runtimes (<= Go 1.9):
// AppEngineTokenSource returns a token source that fetches tokens issued to the
// current App Engine application's service account. The provided context must have
// come from appengine.NewContext.
//
// Second generation App Engine runtimes (>= Go 1.11) and App Engine flexible:
// AppEngineTokenSource is DEPRECATED on second generation runtimes and on the
// flexible environment. It delegates to ComputeTokenSource, and the provided
// context and scopes are not used. Please use DefaultTokenSource (or ComputeTokenSource,
// which DefaultTokenSource will use in this case) instead.
func AppEngineTokenSource(ctx context.Context, scope ...string) oauth2.TokenSource {
return appEngineTokenSource(ctx, scope...)
}

View File

@@ -1,77 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build appengine
// This file applies to App Engine first generation runtimes (<= Go 1.9).
package google
import (
"context"
"sort"
"strings"
"sync"
"golang.org/x/oauth2"
"google.golang.org/appengine"
)
func init() {
appengineTokenFunc = appengine.AccessToken
appengineAppIDFunc = appengine.AppID
}
// See comment on AppEngineTokenSource in appengine.go.
func appEngineTokenSource(ctx context.Context, scope ...string) oauth2.TokenSource {
scopes := append([]string{}, scope...)
sort.Strings(scopes)
return &gaeTokenSource{
ctx: ctx,
scopes: scopes,
key: strings.Join(scopes, " "),
}
}
// aeTokens helps the fetched tokens to be reused until their expiration.
var (
aeTokensMu sync.Mutex
aeTokens = make(map[string]*tokenLock) // key is space-separated scopes
)
type tokenLock struct {
mu sync.Mutex // guards t; held while fetching or updating t
t *oauth2.Token
}
type gaeTokenSource struct {
ctx context.Context
scopes []string
key string // to aeTokens map; space-separated scopes
}
func (ts *gaeTokenSource) Token() (*oauth2.Token, error) {
aeTokensMu.Lock()
tok, ok := aeTokens[ts.key]
if !ok {
tok = &tokenLock{}
aeTokens[ts.key] = tok
}
aeTokensMu.Unlock()
tok.mu.Lock()
defer tok.mu.Unlock()
if tok.t.Valid() {
return tok.t, nil
}
access, exp, err := appengineTokenFunc(ts.ctx, ts.scopes...)
if err != nil {
return nil, err
}
tok.t = &oauth2.Token{
AccessToken: access,
Expiry: exp,
}
return tok.t, nil
}

View File

@@ -1,27 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !appengine
// This file applies to App Engine second generation runtimes (>= Go 1.11) and App Engine flexible.
package google
import (
"context"
"log"
"sync"
"golang.org/x/oauth2"
)
var logOnce sync.Once // only spam about deprecation once
// See comment on AppEngineTokenSource in appengine.go.
func appEngineTokenSource(ctx context.Context, scope ...string) oauth2.TokenSource {
logOnce.Do(func() {
log.Print("google: AppEngineTokenSource is deprecated on App Engine standard second generation runtimes (>= Go 1.11) and App Engine flexible. Please use DefaultTokenSource or ComputeTokenSource.")
})
return ComputeTokenSource("")
}

View File

@@ -12,6 +12,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"runtime" "runtime"
"sync"
"time" "time"
"cloud.google.com/go/compute/metadata" "cloud.google.com/go/compute/metadata"
@@ -41,12 +42,20 @@ type Credentials struct {
// running on Google Cloud Platform. // running on Google Cloud Platform.
JSON []byte JSON []byte
udMu sync.Mutex // guards universeDomain
// universeDomain is the default service domain for a given Cloud universe. // universeDomain is the default service domain for a given Cloud universe.
universeDomain string universeDomain string
} }
// UniverseDomain returns the default service domain for a given Cloud universe. // UniverseDomain returns the default service domain for a given Cloud universe.
//
// The default value is "googleapis.com". // The default value is "googleapis.com".
//
// Deprecated: Use instead (*Credentials).GetUniverseDomain(), which supports
// obtaining the universe domain when authenticating via the GCE metadata server.
// Unlike GetUniverseDomain, this method, UniverseDomain, will always return the
// default value when authenticating via the GCE metadata server.
// See also [The attached service account](https://cloud.google.com/docs/authentication/application-default-credentials#attached-sa).
func (c *Credentials) UniverseDomain() string { func (c *Credentials) UniverseDomain() string {
if c.universeDomain == "" { if c.universeDomain == "" {
return universeDomainDefault return universeDomainDefault
@@ -54,6 +63,55 @@ func (c *Credentials) UniverseDomain() string {
return c.universeDomain return c.universeDomain
} }
// GetUniverseDomain returns the default service domain for a given Cloud
// universe.
//
// The default value is "googleapis.com".
//
// It obtains the universe domain from the attached service account on GCE when
// authenticating via the GCE metadata server. See also [The attached service
// account](https://cloud.google.com/docs/authentication/application-default-credentials#attached-sa).
// If the GCE metadata server returns a 404 error, the default value is
// returned. If the GCE metadata server returns an error other than 404, the
// error is returned.
func (c *Credentials) GetUniverseDomain() (string, error) {
c.udMu.Lock()
defer c.udMu.Unlock()
if c.universeDomain == "" && metadata.OnGCE() {
// If we're on Google Compute Engine, an App Engine standard second
// generation runtime, or App Engine flexible, use the metadata server.
err := c.computeUniverseDomain()
if err != nil {
return "", err
}
}
// If not on Google Compute Engine, or in case of any non-error path in
// computeUniverseDomain that did not set universeDomain, set the default
// universe domain.
if c.universeDomain == "" {
c.universeDomain = universeDomainDefault
}
return c.universeDomain, nil
}
// computeUniverseDomain fetches the default service domain for a given Cloud
// universe from Google Compute Engine (GCE)'s metadata server. It's only valid
// to use this method if your program is running on a GCE instance.
func (c *Credentials) computeUniverseDomain() error {
var err error
c.universeDomain, err = metadata.Get("universe/universe_domain")
if err != nil {
if _, ok := err.(metadata.NotDefinedError); ok {
// http.StatusNotFound (404)
c.universeDomain = universeDomainDefault
return nil
} else {
return err
}
}
return nil
}
// DefaultCredentials is the old name of Credentials. // DefaultCredentials is the old name of Credentials.
// //
// Deprecated: use Credentials instead. // Deprecated: use Credentials instead.
@@ -91,6 +149,12 @@ type CredentialsParams struct {
// Note: This option is currently only respected when using credentials // Note: This option is currently only respected when using credentials
// fetched from the GCE metadata server. // fetched from the GCE metadata server.
EarlyTokenRefresh time.Duration EarlyTokenRefresh time.Duration
// UniverseDomain is the default service domain for a given Cloud universe.
// Only supported in authentication flows that support universe domains.
// This value takes precedence over a universe domain explicitly specified
// in a credentials config file or by the GCE metadata server. Optional.
UniverseDomain string
} }
func (params CredentialsParams) deepCopy() CredentialsParams { func (params CredentialsParams) deepCopy() CredentialsParams {
@@ -160,16 +224,6 @@ func FindDefaultCredentialsWithParams(ctx context.Context, params CredentialsPar
return CredentialsFromJSONWithParams(ctx, b, params) return CredentialsFromJSONWithParams(ctx, b, params)
} }
// Third, if we're on a Google App Engine standard first generation runtime (<= Go 1.9)
// use those credentials. App Engine standard second generation runtimes (>= Go 1.11)
// and App Engine flexible use ComputeTokenSource and the metadata server.
if appengineTokenFunc != nil {
return &Credentials{
ProjectID: appengineAppIDFunc(ctx),
TokenSource: AppEngineTokenSource(ctx, params.Scopes...),
}, nil
}
// Fourth, if we're on Google Compute Engine, an App Engine standard second generation runtime, // Fourth, if we're on Google Compute Engine, an App Engine standard second generation runtime,
// or App Engine flexible, use the metadata server. // or App Engine flexible, use the metadata server.
if metadata.OnGCE() { if metadata.OnGCE() {
@@ -177,6 +231,7 @@ func FindDefaultCredentialsWithParams(ctx context.Context, params CredentialsPar
return &Credentials{ return &Credentials{
ProjectID: id, ProjectID: id,
TokenSource: computeTokenSource("", params.EarlyTokenRefresh, params.Scopes...), TokenSource: computeTokenSource("", params.EarlyTokenRefresh, params.Scopes...),
universeDomain: params.UniverseDomain,
}, nil }, nil
} }
@@ -217,6 +272,9 @@ func CredentialsFromJSONWithParams(ctx context.Context, jsonData []byte, params
} }
universeDomain := f.UniverseDomain universeDomain := f.UniverseDomain
if params.UniverseDomain != "" {
universeDomain = params.UniverseDomain
}
// Authorized user credentials are only supported in the googleapis.com universe. // Authorized user credentials are only supported in the googleapis.com universe.
if f.Type == userCredentialsKey { if f.Type == userCredentialsKey {
universeDomain = universeDomainDefault universeDomain = universeDomainDefault

View File

@@ -6,6 +6,9 @@ package google
import ( import (
"context" "context"
"net/http"
"net/http/httptest"
"strings"
"testing" "testing"
) )
@@ -53,6 +56,10 @@ var userJSONUniverseDomain = []byte(`{
"universe_domain": "example.com" "universe_domain": "example.com"
}`) }`)
var universeDomain = "example.com"
var universeDomain2 = "apis-tpclp.goog"
func TestCredentialsFromJSONWithParams_SA(t *testing.T) { func TestCredentialsFromJSONWithParams_SA(t *testing.T) {
ctx := context.Background() ctx := context.Background()
scope := "https://www.googleapis.com/auth/cloud-platform" scope := "https://www.googleapis.com/auth/cloud-platform"
@@ -70,6 +77,32 @@ func TestCredentialsFromJSONWithParams_SA(t *testing.T) {
if want := "googleapis.com"; creds.UniverseDomain() != want { if want := "googleapis.com"; creds.UniverseDomain() != want {
t.Fatalf("got %q, want %q", creds.UniverseDomain(), want) t.Fatalf("got %q, want %q", creds.UniverseDomain(), want)
} }
if want := "googleapis.com"; creds.UniverseDomain() != want {
t.Fatalf("got %q, want %q", creds.UniverseDomain(), want)
}
}
func TestCredentialsFromJSONWithParams_SA_Params_UniverseDomain(t *testing.T) {
ctx := context.Background()
scope := "https://www.googleapis.com/auth/cloud-platform"
params := CredentialsParams{
Scopes: []string{scope},
UniverseDomain: universeDomain2,
}
creds, err := CredentialsFromJSONWithParams(ctx, saJSONJWT, params)
if err != nil {
t.Fatal(err)
}
if want := "fake_project"; creds.ProjectID != want {
t.Fatalf("got %q, want %q", creds.ProjectID, want)
}
if creds.UniverseDomain() != universeDomain2 {
t.Fatalf("got %q, want %q", creds.UniverseDomain(), universeDomain2)
}
if creds.UniverseDomain() != universeDomain2 {
t.Fatalf("got %q, want %q", creds.UniverseDomain(), universeDomain2)
}
} }
func TestCredentialsFromJSONWithParams_SA_UniverseDomain(t *testing.T) { func TestCredentialsFromJSONWithParams_SA_UniverseDomain(t *testing.T) {
@@ -86,8 +119,42 @@ func TestCredentialsFromJSONWithParams_SA_UniverseDomain(t *testing.T) {
if want := "fake_project"; creds.ProjectID != want { if want := "fake_project"; creds.ProjectID != want {
t.Fatalf("got %q, want %q", creds.ProjectID, want) t.Fatalf("got %q, want %q", creds.ProjectID, want)
} }
if want := "example.com"; creds.UniverseDomain() != want { if creds.UniverseDomain() != universeDomain {
t.Fatalf("got %q, want %q", creds.UniverseDomain(), want) t.Fatalf("got %q, want %q", creds.UniverseDomain(), universeDomain)
}
got, err := creds.GetUniverseDomain()
if err != nil {
t.Fatal(err)
}
if got != universeDomain {
t.Fatalf("got %q, want %q", got, universeDomain)
}
}
func TestCredentialsFromJSONWithParams_SA_UniverseDomain_Params_UniverseDomain(t *testing.T) {
ctx := context.Background()
scope := "https://www.googleapis.com/auth/cloud-platform"
params := CredentialsParams{
Scopes: []string{scope},
UniverseDomain: universeDomain2,
}
creds, err := CredentialsFromJSONWithParams(ctx, saJSONJWTUniverseDomain, params)
if err != nil {
t.Fatal(err)
}
if want := "fake_project"; creds.ProjectID != want {
t.Fatalf("got %q, want %q", creds.ProjectID, want)
}
if creds.UniverseDomain() != universeDomain2 {
t.Fatalf("got %q, want %q", creds.UniverseDomain(), universeDomain2)
}
got, err := creds.GetUniverseDomain()
if err != nil {
t.Fatal(err)
}
if got != universeDomain2 {
t.Fatalf("got %q, want %q", got, universeDomain2)
} }
} }
@@ -105,6 +172,37 @@ func TestCredentialsFromJSONWithParams_User(t *testing.T) {
if want := "googleapis.com"; creds.UniverseDomain() != want { if want := "googleapis.com"; creds.UniverseDomain() != want {
t.Fatalf("got %q, want %q", creds.UniverseDomain(), want) t.Fatalf("got %q, want %q", creds.UniverseDomain(), want)
} }
got, err := creds.GetUniverseDomain()
if err != nil {
t.Fatal(err)
}
if want := "googleapis.com"; got != want {
t.Fatalf("got %q, want %q", got, want)
}
}
func TestCredentialsFromJSONWithParams_User_Params_UniverseDomain(t *testing.T) {
ctx := context.Background()
scope := "https://www.googleapis.com/auth/cloud-platform"
params := CredentialsParams{
Scopes: []string{scope},
UniverseDomain: universeDomain2,
}
creds, err := CredentialsFromJSONWithParams(ctx, userJSON, params)
if err != nil {
t.Fatal(err)
}
if want := "googleapis.com"; creds.UniverseDomain() != want {
t.Fatalf("got %q, want %q", creds.UniverseDomain(), want)
}
got, err := creds.GetUniverseDomain()
if err != nil {
t.Fatal(err)
}
if want := "googleapis.com"; got != want {
t.Fatalf("got %q, want %q", got, want)
}
} }
func TestCredentialsFromJSONWithParams_User_UniverseDomain(t *testing.T) { func TestCredentialsFromJSONWithParams_User_UniverseDomain(t *testing.T) {
@@ -121,4 +219,79 @@ func TestCredentialsFromJSONWithParams_User_UniverseDomain(t *testing.T) {
if want := "googleapis.com"; creds.UniverseDomain() != want { if want := "googleapis.com"; creds.UniverseDomain() != want {
t.Fatalf("got %q, want %q", creds.UniverseDomain(), want) t.Fatalf("got %q, want %q", creds.UniverseDomain(), want)
} }
got, err := creds.GetUniverseDomain()
if err != nil {
t.Fatal(err)
}
if want := "googleapis.com"; got != want {
t.Fatalf("got %q, want %q", got, want)
}
}
func TestCredentialsFromJSONWithParams_User_UniverseDomain_Params_UniverseDomain(t *testing.T) {
ctx := context.Background()
scope := "https://www.googleapis.com/auth/cloud-platform"
params := CredentialsParams{
Scopes: []string{scope},
UniverseDomain: universeDomain2,
}
creds, err := CredentialsFromJSONWithParams(ctx, userJSONUniverseDomain, params)
if err != nil {
t.Fatal(err)
}
if want := "googleapis.com"; creds.UniverseDomain() != want {
t.Fatalf("got %q, want %q", creds.UniverseDomain(), want)
}
got, err := creds.GetUniverseDomain()
if err != nil {
t.Fatal(err)
}
if want := "googleapis.com"; got != want {
t.Fatalf("got %q, want %q", got, want)
}
}
func TestComputeUniverseDomain(t *testing.T) {
universeDomainPath := "/computeMetadata/v1/universe/universe_domain"
universeDomainResponseBody := "example.com"
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != universeDomainPath {
t.Errorf("got %s, want %s", r.URL.Path, universeDomainPath)
}
w.Write([]byte(universeDomainResponseBody))
}))
defer s.Close()
t.Setenv("GCE_METADATA_HOST", strings.TrimPrefix(s.URL, "http://"))
scope := "https://www.googleapis.com/auth/cloud-platform"
params := CredentialsParams{
Scopes: []string{scope},
}
// Copied from FindDefaultCredentialsWithParams, metadata.OnGCE() = true block
creds := &Credentials{
ProjectID: "fake_project",
TokenSource: computeTokenSource("", params.EarlyTokenRefresh, params.Scopes...),
universeDomain: params.UniverseDomain, // empty
}
c := make(chan bool)
go func() {
got, err := creds.GetUniverseDomain() // First conflicting access.
if err != nil {
t.Error(err)
}
if want := universeDomainResponseBody; got != want {
t.Errorf("got %q, want %q", got, want)
}
c <- true
}()
got, err := creds.GetUniverseDomain() // Second conflicting access.
<-c
if err != nil {
t.Error(err)
}
if want := universeDomainResponseBody; got != want {
t.Errorf("got %q, want %q", got, want)
}
} }

View File

@@ -101,6 +101,8 @@
// executable-sourced credentials), please check out: // executable-sourced credentials), please check out:
// https://cloud.google.com/iam/docs/workforce-obtaining-short-lived-credentials#generate_a_configuration_file_for_non-interactive_sign-in // https://cloud.google.com/iam/docs/workforce-obtaining-short-lived-credentials#generate_a_configuration_file_for_non-interactive_sign-in
// //
// # Security considerations
//
// Note that this library does not perform any validation on the token_url, token_info_url, // Note that this library does not perform any validation on the token_url, token_info_url,
// or service_account_impersonation_url fields of the credential configuration. // or service_account_impersonation_url fields of the credential configuration.
// It is not recommended to use a credential configuration that you did not generate with // It is not recommended to use a credential configuration that you did not generate with

View File

@@ -42,13 +42,16 @@ import (
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"strings"
"time" "time"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
var ( const (
identityBindingEndpoint = "https://sts.googleapis.com/v1/token" universeDomainPlaceholder = "UNIVERSE_DOMAIN"
identityBindingEndpointTemplate = "https://sts.UNIVERSE_DOMAIN/v1/token"
universeDomainDefault = "googleapis.com"
) )
type accessBoundary struct { type accessBoundary struct {
@@ -105,6 +108,18 @@ type DownscopingConfig struct {
// access (or set of accesses) that the new token has to a given resource. // access (or set of accesses) that the new token has to a given resource.
// There can be a maximum of 10 AccessBoundaryRules. // There can be a maximum of 10 AccessBoundaryRules.
Rules []AccessBoundaryRule Rules []AccessBoundaryRule
// UniverseDomain is the default service domain for a given Cloud universe.
// The default value is "googleapis.com". Optional.
UniverseDomain string
}
// identityBindingEndpoint returns the identity binding endpoint with the
// configured universe domain.
func (dc *DownscopingConfig) identityBindingEndpoint() string {
if dc.UniverseDomain == "" {
return strings.Replace(identityBindingEndpointTemplate, universeDomainPlaceholder, universeDomainDefault, 1)
}
return strings.Replace(identityBindingEndpointTemplate, universeDomainPlaceholder, dc.UniverseDomain, 1)
} }
// A downscopingTokenSource is used to retrieve a downscoped token with restricted // A downscopingTokenSource is used to retrieve a downscoped token with restricted
@@ -114,6 +129,9 @@ type downscopingTokenSource struct {
ctx context.Context ctx context.Context
// config holds the information necessary to generate a downscoped Token. // config holds the information necessary to generate a downscoped Token.
config DownscopingConfig config DownscopingConfig
// identityBindingEndpoint is the identity binding endpoint with the
// configured universe domain.
identityBindingEndpoint string
} }
// NewTokenSource returns a configured downscopingTokenSource. // NewTokenSource returns a configured downscopingTokenSource.
@@ -135,7 +153,11 @@ func NewTokenSource(ctx context.Context, conf DownscopingConfig) (oauth2.TokenSo
return nil, fmt.Errorf("downscope: all rules must provide at least one permission: %+v", val) return nil, fmt.Errorf("downscope: all rules must provide at least one permission: %+v", val)
} }
} }
return downscopingTokenSource{ctx: ctx, config: conf}, nil return downscopingTokenSource{
ctx: ctx,
config: conf,
identityBindingEndpoint: conf.identityBindingEndpoint(),
}, nil
} }
// Token() uses a downscopingTokenSource to generate an oauth2 Token. // Token() uses a downscopingTokenSource to generate an oauth2 Token.
@@ -171,7 +193,7 @@ func (dts downscopingTokenSource) Token() (*oauth2.Token, error) {
form.Add("options", string(b)) form.Add("options", string(b))
myClient := oauth2.NewClient(dts.ctx, nil) myClient := oauth2.NewClient(dts.ctx, nil)
resp, err := myClient.PostForm(identityBindingEndpoint, form) resp, err := myClient.PostForm(dts.identityBindingEndpoint, form)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to generate POST Request %v", err) return nil, fmt.Errorf("unable to generate POST Request %v", err)
} }

View File

@@ -38,18 +38,43 @@ func Test_DownscopedTokenSource(t *testing.T) {
w.Write([]byte(standardRespBody)) w.Write([]byte(standardRespBody))
})) }))
new := []AccessBoundaryRule{ myTok := oauth2.Token{AccessToken: "Mellon"}
tmpSrc := oauth2.StaticTokenSource(&myTok)
rules := []AccessBoundaryRule{
{ {
AvailableResource: "test1", AvailableResource: "test1",
AvailablePermissions: []string{"Perm1", "Perm2"}, AvailablePermissions: []string{"Perm1", "Perm2"},
}, },
} }
myTok := oauth2.Token{AccessToken: "Mellon"} dts := downscopingTokenSource{
tmpSrc := oauth2.StaticTokenSource(&myTok) ctx: context.Background(),
dts := downscopingTokenSource{context.Background(), DownscopingConfig{tmpSrc, new}} config: DownscopingConfig{
identityBindingEndpoint = ts.URL RootSource: tmpSrc,
Rules: rules,
},
identityBindingEndpoint: ts.URL,
}
_, err := dts.Token() _, err := dts.Token()
if err != nil { if err != nil {
t.Fatalf("NewDownscopedTokenSource failed with error: %v", err) t.Fatalf("NewDownscopedTokenSource failed with error: %v", err)
} }
} }
func Test_DownscopingConfig(t *testing.T) {
tests := []struct {
universeDomain string
want string
}{
{"", "https://sts.googleapis.com/v1/token"},
{"googleapis.com", "https://sts.googleapis.com/v1/token"},
{"example.com", "https://sts.example.com/v1/token"},
}
for _, tt := range tests {
c := DownscopingConfig{
UniverseDomain: tt.universeDomain,
}
if got := c.identityBindingEndpoint(); got != tt.want {
t.Errorf("got %q, want %q", got, tt.want)
}
}
}

View File

@@ -5,6 +5,8 @@
package google package google
import ( import (
"net/http"
"net/http/httptest"
"strings" "strings"
"testing" "testing"
) )
@@ -137,3 +139,21 @@ func TestJWTConfigFromJSONNoAudience(t *testing.T) {
t.Errorf("Audience = %q; want %q", got, want) t.Errorf("Audience = %q; want %q", got, want)
} }
} }
func TestComputeTokenSource(t *testing.T) {
tokenPath := "/computeMetadata/v1/instance/service-accounts/default/token"
tokenResponseBody := `{"access_token":"Sample.Access.Token","token_type":"Bearer","expires_in":3600}`
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != tokenPath {
t.Errorf("got %s, want %s", r.URL.Path, tokenPath)
}
w.Write([]byte(tokenResponseBody))
}))
defer s.Close()
t.Setenv("GCE_METADATA_HOST", strings.TrimPrefix(s.URL, "http://"))
ts := ComputeTokenSource("")
_, err := ts.Token()
if err != nil {
t.Errorf("ts.Token() = %v", err)
}
}

View File

@@ -19,7 +19,7 @@ import (
"time" "time"
) )
var serviceAccountImpersonationRE = regexp.MustCompile("https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/(.*@.*):generateAccessToken") var serviceAccountImpersonationRE = regexp.MustCompile("https://iamcredentials\\..+/v1/projects/-/serviceAccounts/(.*@.*):generateAccessToken")
const ( const (
executableSupportedMaxVersion = 1 executableSupportedMaxVersion = 1

View File

@@ -1021,3 +1021,37 @@ func TestRetrieveOutputFileSubjectTokenJwt(t *testing.T) {
}) })
} }
} }
func TestServiceAccountImpersonationRE(t *testing.T) {
tests := []struct {
name string
serviceAccountImpersonationURL string
want string
}{
{
name: "universe domain Google Default Universe (GDU) googleapis.com",
serviceAccountImpersonationURL: "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/test@project.iam.gserviceaccount.com:generateAccessToken",
want: "test@project.iam.gserviceaccount.com",
},
{
name: "email does not match",
serviceAccountImpersonationURL: "test@project.iam.gserviceaccount.com",
want: "",
},
{
name: "universe domain non-GDU",
serviceAccountImpersonationURL: "https://iamcredentials.apis-tpclp.goog/v1/projects/-/serviceAccounts/test@project.iam.gserviceaccount.com:generateAccessToken",
want: "test@project.iam.gserviceaccount.com",
},
}
for _, tt := range tests {
matches := serviceAccountImpersonationRE.FindStringSubmatch(tt.serviceAccountImpersonationURL)
if matches == nil {
if tt.want != "" {
t.Errorf("%q: got nil, want %q", tt.name, tt.want)
}
} else if matches[1] != tt.want {
t.Errorf("%q: got %q, want %q", tt.name, matches[1], tt.want)
}
}
}

View File

@@ -1,13 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build appengine
package internal
import "google.golang.org/appengine/urlfetch"
func init() {
appengineClientHook = urlfetch.Client
}

View File

@@ -18,16 +18,11 @@ var HTTPClient ContextKey
// because nobody else can create a ContextKey, being unexported. // because nobody else can create a ContextKey, being unexported.
type ContextKey struct{} type ContextKey struct{}
var appengineClientHook func(context.Context) *http.Client
func ContextClient(ctx context.Context) *http.Client { func ContextClient(ctx context.Context) *http.Client {
if ctx != nil { if ctx != nil {
if hc, ok := ctx.Value(HTTPClient).(*http.Client); ok { if hc, ok := ctx.Value(HTTPClient).(*http.Client); ok {
return hc return hc
} }
} }
if appengineClientHook != nil {
return appengineClientHook(ctx)
}
return http.DefaultClient return http.DefaultClient
} }