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

Universe domain requirements tracking #2357

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 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
3f5cbc2
Revert "restore transport"
quartzmo Jan 17, 2024
0cab034
Revert "restore option"
quartzmo Jan 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 29 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(
"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,17 @@ 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
}
// TODO(chrisdsmith): Closes: AL-9 (remove this note before publication)
if settings.UniverseDomainNotGDU() {
return nil, ErrUniverseNotSupportedDomainWideDelegation
}
return user(ctx, config, client, lifetime, isStaticToken)
}

Expand All @@ -113,6 +126,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.
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
28 changes: 28 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,17 @@ func getClientCertificateSourceAndEndpoint(settings *DialSettings) (cert.Source,
if err != nil {
return nil, "", err
}
// TODO(chrisdsmith): Closes: CL-R1 (remove this note before publication)
// TODO(chrisdsmith): Closes: CL-R3 (remove this note before publication)
// TODO(chrisdsmith): Use this composed endpoint everywhere to replace DialSettings.DefaultEndpoint
// 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 +112,12 @@ func getTransportConfig(settings *DialSettings) (*transportConfig, error) {
if !shouldUseS2A(clientCertSource, settings) {
return &defaultTransportConfig, nil
}
// TODO(chrisdsmith): Closes: CL-R10 (remove this note before publication)
if settings.UniverseDomainNotGDU() {
return &transportConfig{
clientCertSource: nil, endpoint: "", s2aAddress: "", s2aMTLSEndpoint: "",
}, ErrUniverseNotSupportedMTLS
}

s2aMTLSEndpoint := settings.DefaultMTLSEndpoint
// If there is endpoint override, honor it.
Expand Down Expand Up @@ -155,6 +179,10 @@ func getEndpoint(settings *DialSettings, clientCertSource cert.Source) (string,
if settings.Endpoint == "" {
mtlsMode := getMTLSMode()
if mtlsMode == mTLSModeAlways || (clientCertSource != nil && mtlsMode == mTLSModeAuto) {
// TODO(chrisdsmith): Closes: CL-R10 (remove this note before publication)
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) {
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)
}
}
}
}
Loading