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

feat: add universe domain support #2296

Merged
merged 28 commits into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
407b668
feat(option): add universe domain support
quartzmo Dec 8, 2023
f850927
feat(internal): add universe domain support
quartzmo Dec 8, 2023
9e47d5f
feat(transport): add universe domain support
quartzmo Dec 8, 2023
d851dd2
Merge branch 'main' into universe-domain
quartzmo Dec 18, 2023
f327e1d
add ErrUniverseNotSupportedMTLS to cba.go
quartzmo Dec 18, 2023
32c2fe5
remove CL-R8 descriptions from option.go, pending decision on CL-R8
quartzmo Dec 18, 2023
0f3cadd
Merge branch 'main' into universe-domain
quartzmo Dec 21, 2023
f649422
feat(impersonate) add universe domain support
quartzmo Dec 21, 2023
6f9cf15
feat(option) add universe domain support
quartzmo Dec 21, 2023
b93a905
Merge branch 'main' into universe-domain
quartzmo Dec 28, 2023
c3d8d0e
Merge branch 'main' into universe-domain
quartzmo Jan 9, 2024
e453234
use universe domain placeholder UNIVERSE_DOMAIN
quartzmo Jan 9, 2024
21289e8
Merge branch 'main' into universe-domain
quartzmo Jan 10, 2024
b698c14
fix isSelfSignedJWTFlow for AL-8
quartzmo Jan 11, 2024
dfc4c18
Merge branch 'main' into universe-domain
quartzmo Jan 17, 2024
c59f588
restore transport
quartzmo Jan 17, 2024
56e635c
restore option
quartzmo Jan 17, 2024
089f402
Merge branch 'main' into universe-domain
quartzmo Jan 17, 2024
83b34eb
add missing line end to user-account.json
quartzmo Jan 17, 2024
007e93c
add temporary guard against missing DefaultEndpointTemplate in getCli…
quartzmo Jan 17, 2024
b19015b
remove requirements tracking comments
quartzmo Jan 17, 2024
46ea4ed
Merge branch 'main' into universe-domain
quartzmo Jan 18, 2024
b9ed8b4
return nil instead of empty struct from getTransportConfig when err
quartzmo Jan 18, 2024
7fb62d8
rename UniverseDomainNotGDU to IsUniverseDomainGDU and invert logic
quartzmo Jan 18, 2024
82ad920
Update errors to unexported types
quartzmo Jan 18, 2024
15b1c22
move TODO instructions to GH issue
quartzmo Jan 18, 2024
f37a18c
update impersonate test
quartzmo Jan 18, 2024
11c12e6
Merge branch 'main' into universe-domain
quartzmo Jan 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
32 changes: 28 additions & 4 deletions impersonate/impersonate.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,25 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"time"

"golang.org/x/oauth2"
"google.golang.org/api/internal"
"google.golang.org/api/option"
"google.golang.org/api/option/internaloption"
htransport "google.golang.org/api/transport/http"
)

var (
iamCredentailsEndpoint = "https://iamcredentials.googleapis.com"
oauth2Endpoint = "https://oauth2.googleapis.com"
iamCredentailsEndpoint = "https://iamcredentials.googleapis.com"
oauth2Endpoint = "https://oauth2.googleapis.com"
ErrUniverseNotSupportedDomainWideDelegation = errors.New(
quartzmo marked this conversation as resolved.
Show resolved Hide resolved
"impersonate: service account user is configured for the credential. Domain-wide " +
"delegation is not supported in universes other than googleapis.com")
)

// CredentialsConfig for generating impersonated credentials.
Expand Down Expand Up @@ -86,9 +91,16 @@ func CredentialsTokenSource(ctx context.Context, config CredentialsConfig, opts
if err != nil {
return nil, err
}
// If a subject is specified a different auth-flow is initiated to
// impersonate as the provided subject (user).
// If a subject is specified a domain-wide delegation auth-flow is initiated
// to impersonate as the provided subject (user).
if config.Subject != "" {
settings, err := newSettings(clientOpts)
if err != nil {
return nil, err
}
if settings.UniverseDomainNotGDU() {
return nil, ErrUniverseNotSupportedDomainWideDelegation
}
return user(ctx, config, client, lifetime, isStaticToken)
}

Expand All @@ -113,6 +125,18 @@ func CredentialsTokenSource(ctx context.Context, config CredentialsConfig, opts
return oauth2.ReuseTokenSource(nil, its), nil
}

func newSettings(opts []option.ClientOption) (*internal.DialSettings, error) {
var o internal.DialSettings
for _, opt := range opts {
opt.Apply(&o)
}
if err := o.Validate(); err != nil {
return nil, err
}

return &o, nil
}

func formatIAMServiceAccountName(name string) string {
return fmt.Sprintf("projects/-/serviceAccounts/%s", name)
}
Expand Down
2 changes: 2 additions & 0 deletions impersonate/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import (
"golang.org/x/oauth2"
)

// user provides an auth flow for domain-wide delegation, setting
// CredentialsConfig.Subject to be the the impersonated user.
quartzmo marked this conversation as resolved.
Show resolved Hide resolved
func user(ctx context.Context, c CredentialsConfig, client *http.Client, lifetime time.Duration, isStaticToken bool) (oauth2.TokenSource, error) {
u := userTokenSource{
client: client,
Expand Down
26 changes: 20 additions & 6 deletions impersonate/user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func TestTokenSource_user(t *testing.T) {
lifetime time.Duration
subject string
wantErr bool
universeDomain string
}{
{
name: "missing targetPrincipal",
Expand All @@ -50,6 +51,16 @@ func TestTokenSource_user(t *testing.T) {
subject: "admin@example.com",
wantErr: false,
},
{
name: "universeDomain",
targetPrincipal: "foo@project-id.iam.gserviceaccount.com",
scopes: []string{"scope"},
subject: "admin@example.com",
wantErr: true,
// Non-GDU Universe Domain should result in error if
// CredentialsConfig.Subject is present for domain-wide delegation.
universeDomain: "example.com",
},
}

for _, tt := range tests {
Expand Down Expand Up @@ -92,12 +103,15 @@ func TestTokenSource_user(t *testing.T) {
return nil
}),
}
ts, err := CredentialsTokenSource(ctx, CredentialsConfig{
TargetPrincipal: tt.targetPrincipal,
Scopes: tt.scopes,
Lifetime: tt.lifetime,
Subject: tt.subject,
}, option.WithHTTPClient(client))
ts, err := CredentialsTokenSource(ctx,
CredentialsConfig{
TargetPrincipal: tt.targetPrincipal,
Scopes: tt.scopes,
Lifetime: tt.lifetime,
Subject: tt.subject,
},
option.WithHTTPClient(client),
option.WithUniverseDomain(tt.universeDomain))
if tt.wantErr && err != nil {
return
}
Expand Down
24 changes: 24 additions & 0 deletions internal/cba.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ package internal
import (
"context"
"crypto/tls"
"errors"
"net"
"net/url"
"os"
Expand All @@ -53,6 +54,12 @@ const (

// Experimental: if true, the code will try MTLS with S2A as the default for transport security. Default value is false.
googleAPIUseS2AEnv = "EXPERIMENTAL_GOOGLE_API_USE_S2A"

universeDomainPlaceholder = "UNIVERSE_DOMAIN"
)

var (
ErrUniverseNotSupportedMTLS = errors.New("mTLS is not supported in any universe other than googleapis.com")
)

// getClientCertificateSourceAndEndpoint is a convenience function that invokes
Expand All @@ -67,6 +74,15 @@ func getClientCertificateSourceAndEndpoint(settings *DialSettings) (cert.Source,
if err != nil {
return nil, "", err
}
// TODO(chrisdsmith): Use this composed endpoint everywhere to replace DialSettings.DefaultEndpoint
quartzmo marked this conversation as resolved.
Show resolved Hide resolved
// TODO(chrisdsmith): Remove settings.DefaultEndpointTemplate != "" condition after rollout of WithDefaultEndpointTemplate is complete.
if settings.Endpoint == "" && settings.UniverseDomainNotGDU() && settings.DefaultEndpointTemplate != "" {
// TODO(chrisdsmith): Uncomment error check below after rollout of WithDefaultEndpointTemplate is complete.
// if settings.DefaultEndpointTemplate == "" {
// return nil, "", errors.New("internaloption.WithDefaultEndpointTemplate is required if option.WithUniverseDomain is not googleapis.com")
// }
endpoint = strings.Replace(settings.DefaultEndpointTemplate, universeDomainPlaceholder, settings.GetUniverseDomain(), 1)
}
return clientCertSource, endpoint, nil
}

Expand Down Expand Up @@ -94,6 +110,11 @@ func getTransportConfig(settings *DialSettings) (*transportConfig, error) {
if !shouldUseS2A(clientCertSource, settings) {
return &defaultTransportConfig, nil
}
if settings.UniverseDomainNotGDU() {
return &transportConfig{
quartzmo marked this conversation as resolved.
Show resolved Hide resolved
clientCertSource: nil, endpoint: "", s2aAddress: "", s2aMTLSEndpoint: "",
}, ErrUniverseNotSupportedMTLS
}

s2aMTLSEndpoint := settings.DefaultMTLSEndpoint
// If there is endpoint override, honor it.
Expand Down Expand Up @@ -155,6 +176,9 @@ func getEndpoint(settings *DialSettings, clientCertSource cert.Source) (string,
if settings.Endpoint == "" {
mtlsMode := getMTLSMode()
if mtlsMode == mTLSModeAlways || (clientCertSource != nil && mtlsMode == mTLSModeAuto) {
if settings.UniverseDomainNotGDU() {
return "", ErrUniverseNotSupportedMTLS
}
return settings.DefaultMTLSEndpoint, nil
}
return settings.DefaultEndpoint, nil
Expand Down
154 changes: 149 additions & 5 deletions internal/cba_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ import (
)

const (
testMTLSEndpoint = "test.mtls.endpoint"
testRegularEndpoint = "test.endpoint"
testOverrideEndpoint = "test.override.endpoint"
testMTLSEndpoint = "https://test.mtls.googleapis.com/"
testRegularEndpoint = "https://test.googleapis.com/"
testEndpointTemplate = "https://test.UNIVERSE_DOMAIN/"
testOverrideEndpoint = "https://test.override.example.com/"
testUniverseDomain = "example.com"
testUniverseDomainEndpoint = "https://test.example.com/"
)

var dummyClientCertSource = func(info *tls.CertificateRequestInfo) (*tls.Certificate, error) { return nil, nil }
Expand Down Expand Up @@ -213,7 +216,7 @@ func TestGetGRPCTransportConfigAndEndpoint(t *testing.T) {
}
}

func TestGetHTTPTransportConfigAndEndpoint(t *testing.T) {
func TestGetHTTPTransportConfigAndEndpoint_s2a(t *testing.T) {
testCases := []struct {
Desc string
InputSettings *DialSettings
Expand Down Expand Up @@ -325,7 +328,10 @@ func TestGetHTTPTransportConfigAndEndpoint(t *testing.T) {
} else {
os.Setenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")
}
_, dialFunc, endpoint, _ := GetHTTPTransportConfigAndEndpoint(tc.InputSettings)
_, dialFunc, endpoint, err := GetHTTPTransportConfigAndEndpoint(tc.InputSettings)
if err != nil {
t.Fatalf("%s: err: %v", tc.Desc, err)
}
if tc.WantEndpoint != endpoint {
t.Errorf("%s: want endpoint: [%s], got [%s]", tc.Desc, tc.WantEndpoint, endpoint)
}
Expand Down Expand Up @@ -355,3 +361,141 @@ func setupTest() func() {
os.Setenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", oldUseClientCert)
}
}

func TestGetHTTPTransportConfigAndEndpoint_UniverseDomain(t *testing.T) {
testCases := []struct {
name string
ds *DialSettings
wantEndpoint string
wantErr error
}{
{
name: "google default universe (GDU), no client cert",
ds: &DialSettings{
DefaultEndpoint: testRegularEndpoint,
DefaultEndpointTemplate: testEndpointTemplate,
DefaultMTLSEndpoint: testMTLSEndpoint,
},
wantEndpoint: testRegularEndpoint,
},
{
name: "google default universe (GDU), client cert",
ds: &DialSettings{
DefaultEndpoint: testRegularEndpoint,
DefaultEndpointTemplate: testEndpointTemplate,
DefaultMTLSEndpoint: testMTLSEndpoint,
ClientCertSource: dummyClientCertSource,
},
wantEndpoint: testMTLSEndpoint,
},
{
name: "UniverseDomain, no client cert",
ds: &DialSettings{
DefaultEndpoint: testRegularEndpoint,
DefaultEndpointTemplate: testEndpointTemplate,
DefaultMTLSEndpoint: testMTLSEndpoint,
UniverseDomain: testUniverseDomain,
},
wantEndpoint: testUniverseDomainEndpoint,
},
{
name: "UniverseDomain, client cert",
ds: &DialSettings{
DefaultEndpoint: testRegularEndpoint,
DefaultEndpointTemplate: testEndpointTemplate,
DefaultMTLSEndpoint: testMTLSEndpoint,
UniverseDomain: testUniverseDomain,
ClientCertSource: dummyClientCertSource,
},
wantEndpoint: testUniverseDomainEndpoint,
wantErr: ErrUniverseNotSupportedMTLS,
},
}

for _, tc := range testCases {
if tc.ds.ClientCertSource != nil {
os.Setenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "true")
} else {
os.Setenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")
}
_, _, endpoint, err := GetHTTPTransportConfigAndEndpoint(tc.ds)
if err != nil {
if err != tc.wantErr {
t.Fatalf("%s: err: %v", tc.name, err)
}
} else {
if tc.wantEndpoint != endpoint {
t.Errorf("%s: want endpoint: [%s], got [%s]", tc.name, tc.wantEndpoint, endpoint)
}
}
}
}

func TestGetGRPCTransportConfigAndEndpoint_UniverseDomain(t *testing.T) {
quartzmo marked this conversation as resolved.
Show resolved Hide resolved
testCases := []struct {
name string
ds *DialSettings
wantEndpoint string
wantErr error
}{
{
name: "google default universe (GDU), no client cert",
ds: &DialSettings{
DefaultEndpoint: testRegularEndpoint,
DefaultEndpointTemplate: testEndpointTemplate,
DefaultMTLSEndpoint: testMTLSEndpoint,
},
wantEndpoint: testRegularEndpoint,
},
{
name: "google default universe (GDU), client cert",
ds: &DialSettings{
DefaultEndpoint: testRegularEndpoint,
DefaultEndpointTemplate: testEndpointTemplate,
DefaultMTLSEndpoint: testMTLSEndpoint,
ClientCertSource: dummyClientCertSource,
},
wantEndpoint: testMTLSEndpoint,
},
{
name: "UniverseDomain, no client cert",
ds: &DialSettings{
DefaultEndpoint: testRegularEndpoint,
DefaultEndpointTemplate: testEndpointTemplate,
DefaultMTLSEndpoint: testMTLSEndpoint,
UniverseDomain: testUniverseDomain,
},
wantEndpoint: testUniverseDomainEndpoint,
},
{
name: "UniverseDomain, client cert",
ds: &DialSettings{
DefaultEndpoint: testRegularEndpoint,
DefaultEndpointTemplate: testEndpointTemplate,
DefaultMTLSEndpoint: testMTLSEndpoint,
UniverseDomain: testUniverseDomain,
ClientCertSource: dummyClientCertSource,
},
wantEndpoint: testUniverseDomainEndpoint,
wantErr: ErrUniverseNotSupportedMTLS,
},
}

for _, tc := range testCases {
if tc.ds.ClientCertSource != nil {
os.Setenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "true")
} else {
os.Setenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")
}
_, endpoint, err := GetGRPCTransportConfigAndEndpoint(tc.ds)
if err != nil {
if err != tc.wantErr {
t.Fatalf("%s: err: %v", tc.name, err)
}
} else {
if tc.wantEndpoint != endpoint {
t.Errorf("%s: want endpoint: [%s], got [%s]", tc.name, tc.wantEndpoint, endpoint)
}
}
}
}