Skip to content

Commit 3f405d4

Browse files
authored
feat: add support for per-dial IAM AuthN config (#713)
This commit adds a new option WithDialIAMAuthN that allows callers to enable or disable IAM AuthN on a per-dial basis, regardless of how the underlying Dialer is configured. Most clients will configure IAM AuthN on a dialer basis and not need this option. However, in some advanced uses, it is necessary to enable or disable IAM AuthN without recreating a dialer. This new option supports that use case.
1 parent 227c2d9 commit 3f405d4

File tree

3 files changed

+92
-36
lines changed

3 files changed

+92
-36
lines changed

dialer.go

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,6 @@ type Dialer struct {
170170
// network. By default it is golang.org/x/net/proxy#Dial.
171171
dialFunc func(cxt context.Context, network, addr string) (net.Conn, error)
172172

173-
useIAMAuthN bool
174173
iamTokenSource oauth2.TokenSource
175174
userAgent string
176175

@@ -201,6 +200,7 @@ func NewDialer(ctx context.Context, opts ...Option) (*Dialer, error) {
201200
dialCfg := dialCfg{
202201
ipType: alloydb.PrivateIP,
203202
tcpKeepAlive: defaultTCPKeepAlive,
203+
iamAuthN: cfg.useIAMAuthN, // Use dialer configuration as default
204204
}
205205
for _, opt := range cfg.dialOpts {
206206
opt(&dialCfg)
@@ -233,7 +233,6 @@ func NewDialer(ctx context.Context, opts ...Option) (*Dialer, error) {
233233
dialerID: dialerID,
234234
metricRecorders: map[alloydb.InstanceURI]telv2.MetricRecorder{},
235235
dialFunc: cfg.dialFunc,
236-
useIAMAuthN: cfg.useIAMAuthN,
237236
iamTokenSource: cfg.tokenSource,
238237
userAgent: userAgent,
239238
buffer: newBuffer(),
@@ -271,6 +270,10 @@ func (d *Dialer) Dial(ctx context.Context, instance string, opts ...DialOption)
271270
return nil, ErrDialerClosed
272271
default:
273272
}
273+
cfg := d.defaultDialCfg
274+
for _, opt := range opts {
275+
opt(&cfg)
276+
}
274277

275278
inst, err := alloydb.ParseInstURI(instance)
276279
if err != nil {
@@ -282,7 +285,7 @@ func (d *Dialer) Dial(ctx context.Context, instance string, opts ...DialOption)
282285
startTime = time.Now()
283286
endDial tel.EndSpanFunc
284287
attrs = telv2.Attributes{
285-
IAMAuthN: d.useIAMAuthN,
288+
IAMAuthN: cfg.iamAuthN,
286289
UserAgent: d.userAgent,
287290
RefreshType: telv2.RefreshAheadType,
288291
}
@@ -300,11 +303,6 @@ func (d *Dialer) Dial(ctx context.Context, instance string, opts ...DialOption)
300303
endDial(err)
301304
}()
302305

303-
cfg := d.defaultDialCfg
304-
for _, opt := range opts {
305-
opt(&cfg)
306-
}
307-
308306
var endInfo tel.EndSpanFunc
309307
ctx, endInfo = tel.StartSpan(ctx, "cloud.google.com/go/alloydbconn/internal.InstanceInfo")
310308
cache, cacheHit, err := d.connectionInfoCache(ctx, inst, mr)
@@ -400,7 +398,7 @@ func (d *Dialer) Dial(ctx context.Context, instance string, opts ...DialOption)
400398
if !d.disableMetadataExchange {
401399
// The metadata exchange must occur after the TLS connection is established
402400
// to avoid leaking sensitive information.
403-
err = d.metadataExchange(tlsConn)
401+
err = d.metadataExchange(tlsConn, cfg.iamAuthN)
404402
if err != nil {
405403
_ = tlsConn.Close() // best effort close attempt
406404
attrs.DialStatus = telv2.DialMDXError
@@ -480,13 +478,13 @@ func invalidClientCert(
480478
// metadata exchange has succeeded and the connection is complete.
481479
//
482480
// Subsequent interactions with the server use the database protocol.
483-
func (d *Dialer) metadataExchange(conn net.Conn) error {
481+
func (d *Dialer) metadataExchange(conn net.Conn, useIAMAuthN bool) error {
484482
tok, err := d.iamTokenSource.Token()
485483
if err != nil {
486484
return err
487485
}
488486
authType := connectorspb.MetadataExchangeRequest_DB_NATIVE
489-
if d.useIAMAuthN {
487+
if useIAMAuthN {
490488
authType = connectorspb.MetadataExchangeRequest_AUTO_IAM
491489
}
492490
req := &connectorspb.MetadataExchangeRequest{

e2e_test.go

Lines changed: 73 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -312,35 +312,83 @@ func TestAutoIAMAuthN(t *testing.T) {
312312
if testing.Short() {
313313
t.Skip("skipping integration test")
314314
}
315-
ctx := context.Background()
316-
317-
d, err := alloydbconn.NewDialer(ctx, alloydbconn.WithIAMAuthN(), alloydbconn.WithOptOutOfBuiltInTelemetry())
318-
if err != nil {
319-
t.Fatalf("failed to init Dialer: %v", err)
315+
tcs := []struct {
316+
desc string
317+
opts []alloydbconn.Option
318+
dialOpts []alloydbconn.DialOption
319+
wantIAMAuthN bool
320+
}{
321+
{
322+
desc: "default behavior",
323+
opts: []alloydbconn.Option{
324+
alloydbconn.WithIAMAuthN(),
325+
alloydbconn.WithOptOutOfBuiltInTelemetry(),
326+
},
327+
wantIAMAuthN: true,
328+
},
329+
{
330+
desc: "dialer with IAM authn off, dial on",
331+
opts: []alloydbconn.Option{
332+
alloydbconn.WithOptOutOfBuiltInTelemetry(),
333+
},
334+
dialOpts: []alloydbconn.DialOption{
335+
alloydbconn.WithDialIAMAuthN(true),
336+
},
337+
wantIAMAuthN: true,
338+
},
339+
{
340+
desc: "dialer with IAM authn on, dial off",
341+
opts: []alloydbconn.Option{
342+
alloydbconn.WithIAMAuthN(),
343+
alloydbconn.WithOptOutOfBuiltInTelemetry(),
344+
},
345+
dialOpts: []alloydbconn.DialOption{
346+
alloydbconn.WithDialIAMAuthN(false),
347+
},
348+
wantIAMAuthN: false,
349+
},
320350
}
351+
for _, tc := range tcs {
352+
t.Run(tc.desc, func(t *testing.T) {
353+
ctx := context.Background()
321354

322-
dsn := fmt.Sprintf(
323-
"user=%s dbname=%s sslmode=disable",
324-
alloydbIAMUser, alloydbDB,
325-
)
326-
config, err := pgx.ParseConfig(dsn)
327-
if err != nil {
328-
t.Fatalf("failed to parse pgx config: %v", err)
329-
}
355+
d, err := alloydbconn.NewDialer(ctx, tc.opts...)
356+
if err != nil {
357+
t.Fatalf("failed to init Dialer: %v", err)
358+
}
330359

331-
config.DialFunc = func(ctx context.Context, _, _ string) (net.Conn, error) {
332-
return d.Dial(ctx, alloydbInstanceName)
333-
}
360+
var (
361+
password = "ignored"
362+
user = alloydbIAMUser
363+
)
364+
if !tc.wantIAMAuthN {
365+
user = alloydbUser
366+
password = alloydbPass
367+
}
368+
dsn := fmt.Sprintf(
369+
"user=%s dbname=%s password=%s sslmode=disable",
370+
user, alloydbDB, password,
371+
)
372+
config, err := pgx.ParseConfig(dsn)
373+
if err != nil {
374+
t.Fatalf("failed to parse pgx config: %v", err)
375+
}
334376

335-
conn, connErr := pgx.ConnectConfig(ctx, config)
336-
if connErr != nil {
337-
t.Fatalf("failed to connect: %s", connErr)
338-
}
339-
defer conn.Close(ctx)
377+
config.DialFunc = func(ctx context.Context, _, _ string) (net.Conn, error) {
378+
return d.Dial(ctx, alloydbInstanceName, tc.dialOpts...)
379+
}
340380

341-
var tt time.Time
342-
if err := conn.QueryRow(context.Background(), "SELECT NOW()").Scan(&tt); err != nil {
343-
t.Fatal(err)
381+
conn, connErr := pgx.ConnectConfig(ctx, config)
382+
if connErr != nil {
383+
t.Fatalf("failed to connect: %s", connErr)
384+
}
385+
defer conn.Close(ctx)
386+
387+
var tt time.Time
388+
if err := conn.QueryRow(context.Background(), "SELECT NOW()").Scan(&tt); err != nil {
389+
t.Fatal(err)
390+
}
391+
t.Log(tt)
392+
})
344393
}
345-
t.Log(tt)
346394
}

options.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ type dialCfg struct {
364364
dialFunc func(ctx context.Context, network, addr string) (net.Conn, error)
365365
ipType string
366366
tcpKeepAlive time.Duration
367+
iamAuthN bool
367368
}
368369

369370
// DialOptions turns a list of DialOption instances into an DialOption.
@@ -415,3 +416,12 @@ func WithPSC() DialOption {
415416
cfg.ipType = alloydb.PSC
416417
}
417418
}
419+
420+
// WithDialIAMAuthN allows calls to Dial to enable or disable IAM AuthN on a
421+
// one-off basis, regardless whether the dialer itself is configured with IAM
422+
// AuthN. There is no performance penalty to using this option.
423+
func WithDialIAMAuthN(enabled bool) DialOption {
424+
return func(cfg *dialCfg) {
425+
cfg.iamAuthN = enabled
426+
}
427+
}

0 commit comments

Comments
 (0)