Skip to content

Commit 01af8ca

Browse files
committed
fix: Set the universe domain when using an impersonation chain.
This change ensures that the universe domain configuration is correctly passed when setting up an impersonation chain for credentials. It also introduces internal test infrastructure to mock `impersonate.CredentialsTokenSource` and adds a unit test `TestCredentialsOpt` to verify this behavior.
1 parent 5b14b6a commit 01af8ca

File tree

2 files changed

+57
-10
lines changed

2 files changed

+57
-10
lines changed

internal/proxy/internal_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,16 @@
1515
package proxy
1616

1717
import (
18+
"context"
19+
"os"
1820
"testing"
1921
"unsafe"
2022

23+
"github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/log"
2124
"github.com/google/go-cmp/cmp"
25+
"golang.org/x/oauth2"
26+
"google.golang.org/api/impersonate"
27+
"google.golang.org/api/option"
2228
)
2329

2430
func TestClientUsesSyncAtomicAlignment(t *testing.T) {
@@ -77,3 +83,39 @@ func equalSlice[T comparable](x, y []T) bool {
7783
}
7884
return true
7985
}
86+
87+
func TestCredentialsOpt(t *testing.T) {
88+
// Mock impersonateTokenSource
89+
originalImpersonateTokenSource := impersonateTokenSource
90+
defer func() { impersonateTokenSource = originalImpersonateTokenSource }()
91+
92+
var gotOpts []option.ClientOption
93+
impersonateTokenSource = func(ctx context.Context, config impersonate.CredentialsConfig, opts ...option.ClientOption) (oauth2.TokenSource, error) {
94+
gotOpts = opts
95+
return nil, nil
96+
}
97+
98+
c := Config{
99+
ImpersonationChain: "sa@project.iam.gserviceaccount.com",
100+
UniverseDomain: "example.com",
101+
}
102+
103+
_, err := credentialsOpt(c, log.NewStdLogger(os.Stdout, os.Stderr))
104+
if err != nil {
105+
t.Fatalf("credentialsOpt error: %v", err)
106+
}
107+
108+
// Verify that WithUniverseDomain was passed
109+
// Since we can't inspect ClientOption internals directly easily,
110+
// we are relying on the fact that we passed it.
111+
// In a real mock we might check the type or value if possible,
112+
// or just ensure the length matches what we expect if we know the order.
113+
// However, option.ClientOption is an interface.
114+
// To strictly verify, we would need to see if the option effect is applied.
115+
// But simply checking we got the options passed to the mock is a good first step.
116+
// For this test, we just want to ensure the logic branch was hit and options were passed.
117+
118+
if len(gotOpts) == 0 {
119+
t.Error("expected ClientOptions to be passed to impersonateTokenSource, got none")
120+
}
121+
}

internal/proxy/proxy.go

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,15 @@
1515
package proxy
1616

1717
import (
18+
"cloud.google.com/go/cloudsqlconn"
1819
"context"
1920
"fmt"
21+
"github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cloudsql"
22+
"github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/gcloud"
23+
"golang.org/x/oauth2"
24+
"google.golang.org/api/impersonate"
25+
"google.golang.org/api/option"
26+
"google.golang.org/api/sqladmin/v1"
2027
"io"
2128
"net"
2229
"os"
@@ -26,20 +33,15 @@ import (
2633
"sync"
2734
"sync/atomic"
2835
"time"
29-
30-
"cloud.google.com/go/cloudsqlconn"
31-
"github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/cloudsql"
32-
"github.com/GoogleCloudPlatform/cloud-sql-proxy/v2/internal/gcloud"
33-
"golang.org/x/oauth2"
34-
"google.golang.org/api/impersonate"
35-
"google.golang.org/api/option"
36-
"google.golang.org/api/sqladmin/v1"
3736
)
3837

3938
var (
4039
// Instance connection name is the format <PROJECT>:<REGION>:<INSTANCE>
4140
// Additionally, we have to support legacy "domain-scoped" projects (e.g. "google.com:PROJECT")
4241
connNameRegex = regexp.MustCompile("([^:]+(:[^:]+)?):([^:]+):([^:]+)")
42+
43+
// impersonateTokenSource is a variable to allow mocking in tests.
44+
impersonateTokenSource = impersonate.CredentialsTokenSource
4345
)
4446

4547
// connName represents the "instance connection name", in the format
@@ -342,6 +344,9 @@ func credentialsOpt(c Config, l cloudsql.Logger) (cloudsqlconn.Option, error) {
342344
// credentials token source.
343345
if c.ImpersonationChain != "" {
344346
var iopts []option.ClientOption
347+
if c.UniverseDomain != "" {
348+
iopts = append(iopts, option.WithUniverseDomain(c.UniverseDomain))
349+
}
345350
switch {
346351
case c.Token != "":
347352
l.Infof("Impersonating service account with OAuth2 token")
@@ -365,7 +370,7 @@ func credentialsOpt(c Config, l cloudsql.Logger) (cloudsqlconn.Option, error) {
365370
l.Infof("Impersonating service account with Application Default Credentials")
366371
}
367372
target, delegates := parseImpersonationChain(c.ImpersonationChain)
368-
ts, err := impersonate.CredentialsTokenSource(
373+
ts, err := impersonateTokenSource(
369374
context.Background(),
370375
impersonate.CredentialsConfig{
371376
TargetPrincipal: target,
@@ -379,7 +384,7 @@ func credentialsOpt(c Config, l cloudsql.Logger) (cloudsqlconn.Option, error) {
379384
}
380385

381386
if c.iamAuthNEnabled() {
382-
iamLoginTS, err := impersonate.CredentialsTokenSource(
387+
iamLoginTS, err := impersonateTokenSource(
383388
context.Background(),
384389
impersonate.CredentialsConfig{
385390
TargetPrincipal: target,

0 commit comments

Comments
 (0)