Add support for IAM Auth for Google CloudSQL DBs (#22445)

This commit is contained in:
kpcraig
2023-09-06 17:40:39 -04:00
committed by GitHub
parent 2ca784ad11
commit 2172786316
11 changed files with 1024 additions and 41 deletions

View File

@@ -14,11 +14,19 @@ import (
"github.com/hashicorp/errwrap"
"github.com/hashicorp/go-secure-stdlib/parseutil"
"github.com/hashicorp/go-uuid"
"github.com/hashicorp/vault/sdk/database/dbplugin"
"github.com/hashicorp/vault/sdk/database/helper/dbutil"
"github.com/mitchellh/mapstructure"
)
const (
AuthTypeGCPIAM = "gcp_iam"
dbTypePostgres = "pgx"
cloudSQLPostgres = "cloudsql-postgres"
)
var _ ConnectionProducer = &SQLConnectionProducer{}
// SQLConnectionProducer implements ConnectionProducer and provides a generic producer for most sql databases
@@ -29,8 +37,15 @@ type SQLConnectionProducer struct {
MaxConnectionLifetimeRaw interface{} `json:"max_connection_lifetime" mapstructure:"max_connection_lifetime" structs:"max_connection_lifetime"`
Username string `json:"username" mapstructure:"username" structs:"username"`
Password string `json:"password" mapstructure:"password" structs:"password"`
AuthType string `json:"auth_type" mapstructure:"auth_type" structs:"auth_type"`
ServiceAccountJSON string `json:"service_account_json" mapstructure:"service_account_json" structs:"service_account_json"`
DisableEscaping bool `json:"disable_escaping" mapstructure:"disable_escaping" structs:"disable_escaping"`
// cloud options here - cloudDriverName is globally unique, but only needs to be retained for the lifetime
// of driver registration, not across plugin restarts.
cloudDriverName string
cloudDialerCleanup func() error
Type string
RawConfig map[string]interface{}
maxConnectionLifetime time.Duration
@@ -107,6 +122,32 @@ func (c *SQLConnectionProducer) Init(ctx context.Context, conf map[string]interf
return nil, errwrap.Wrapf("invalid max_connection_lifetime: {{err}}", err)
}
// validate auth_type if provided
authType := c.AuthType
if authType != "" {
if ok := ValidateAuthType(authType); !ok {
return nil, fmt.Errorf("invalid auth_type %s provided", authType)
}
}
if authType == AuthTypeGCPIAM {
c.cloudDriverName, err = uuid.GenerateUUID()
if err != nil {
return nil, fmt.Errorf("unable to generate UUID for IAM configuration: %w", err)
}
// for _most_ sql databases, the driver itself contains no state. In the case of google's cloudsql drivers,
// however, the driver might store a credentials file, in which case the state stored by the driver is in
// fact critical to the proper function of the connection. So it needs to be registered here inside the
// ConnectionProducer init.
dialerCleanup, err := c.registerDrivers(c.cloudDriverName, c.ServiceAccountJSON)
if err != nil {
return nil, err
}
c.cloudDialerCleanup = dialerCleanup
}
// Set initialized to true at this point since all fields are set,
// and the connection can be established at a later time.
c.Initialized = true
@@ -137,12 +178,24 @@ func (c *SQLConnectionProducer) Connection(ctx context.Context) (interface{}, er
// If the ping was unsuccessful, close it and ignore errors as we'll be
// reestablishing anyways
c.db.Close()
// if IAM authentication is enabled
// ensure open dialer is also closed
if c.AuthType == AuthTypeGCPIAM {
if c.cloudDialerCleanup != nil {
c.cloudDialerCleanup()
}
}
}
// For mssql backend, switch to sqlserver instead
dbType := c.Type
if c.Type == "mssql" {
dbType = "sqlserver"
// default non-IAM behavior
driverName := c.Type
if c.AuthType == AuthTypeGCPIAM {
driverName = c.cloudDriverName
} else if c.Type == "mssql" {
// For mssql backend, switch to sqlserver instead
driverName = "sqlserver"
}
// Otherwise, attempt to make connection
@@ -164,7 +217,7 @@ func (c *SQLConnectionProducer) Connection(ctx context.Context) (interface{}, er
}
var err error
c.db, err = sql.Open(dbType, conn)
c.db, err = sql.Open(driverName, conn)
if err != nil {
return nil, err
}
@@ -192,6 +245,13 @@ func (c *SQLConnectionProducer) Close() error {
if c.db != nil {
c.db.Close()
// cleanup IAM dialer if it exists
if c.AuthType == AuthTypeGCPIAM {
if c.cloudDialerCleanup != nil {
c.cloudDialerCleanup()
}
}
}
c.db = nil