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

@@ -12,14 +12,17 @@ import (
"testing"
"time"
"github.com/hashicorp/vault/sdk/database/helper/connutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/hashicorp/vault/helper/testhelpers/postgresql"
"github.com/hashicorp/vault/sdk/database/dbplugin/v5"
dbtesting "github.com/hashicorp/vault/sdk/database/dbplugin/v5/testing"
"github.com/hashicorp/vault/sdk/database/helper/dbutil"
"github.com/hashicorp/vault/sdk/helper/docker"
"github.com/hashicorp/vault/sdk/helper/template"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func getPostgreSQL(t *testing.T, options map[string]interface{}) (*PostgreSQL, func()) {
@@ -94,6 +97,93 @@ func TestPostgreSQL_Initialize_ConnURLWithDSNFormat(t *testing.T) {
}
}
// Ensures we can successfully initialize and connect to a CloudSQL database
// Requires the following:
// - GOOGLE_APPLICATION_CREDENTIALS either JSON or path to file
// - CONNECTION_URL to a valid Postgres instance on Google CloudSQL
func TestPostgreSQL_Initialize_CloudGCP(t *testing.T) {
envConnURL := "CONNECTION_URL"
connURL := os.Getenv(envConnURL)
if connURL == "" {
t.Skipf("env var %s not set, skipping test", envConnURL)
}
credStr := dbtesting.GetGCPTestCredentials(t)
type testCase struct {
req dbplugin.InitializeRequest
wantErr bool
expectedError string
}
tests := map[string]testCase{
"empty auth type": {
req: dbplugin.InitializeRequest{
Config: map[string]interface{}{
"connection_url": connURL,
"auth_type": "",
},
},
},
"invalid auth type": {
req: dbplugin.InitializeRequest{
Config: map[string]interface{}{
"connection_url": connURL,
"auth_type": "invalid",
},
},
wantErr: true,
expectedError: "invalid auth_type",
},
"default credentials": {
req: dbplugin.InitializeRequest{
Config: map[string]interface{}{
"connection_url": connURL,
"auth_type": connutil.AuthTypeGCPIAM,
},
VerifyConnection: true,
},
},
"JSON credentials": {
req: dbplugin.InitializeRequest{
Config: map[string]interface{}{
"connection_url": connURL,
"auth_type": connutil.AuthTypeGCPIAM,
"service_account_json": credStr,
},
VerifyConnection: true,
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
db := new()
defer dbtesting.AssertClose(t, db)
_, err := dbtesting.VerifyInitialize(t, db, test.req)
if test.wantErr {
if err == nil {
t.Fatalf("expected error but received nil")
}
if !strings.Contains(err.Error(), test.expectedError) {
t.Fatalf("expected error %s, got %s", test.expectedError, err.Error())
}
} else {
if err != nil {
t.Fatalf("expected no error, received %s", err)
}
if !db.Initialized {
t.Fatal("Database should be initialized")
}
}
})
}
}
// TestPostgreSQL_PasswordAuthentication tests that the default "password_authentication" is "none", and that
// an error is returned if an invalid "password_authentication" is provided.
func TestPostgreSQL_PasswordAuthentication(t *testing.T) {
@@ -1100,6 +1190,86 @@ func TestNewUser_CustomUsername(t *testing.T) {
}
}
func TestNewUser_CloudGCP(t *testing.T) {
envConnURL := "CONNECTION_URL"
connURL := os.Getenv(envConnURL)
if connURL == "" {
t.Skipf("env var %s not set, skipping test", envConnURL)
}
credStr := dbtesting.GetGCPTestCredentials(t)
type testCase struct {
usernameTemplate string
newUserData dbplugin.UsernameMetadata
expectedRegex string
}
tests := map[string]testCase{
"default template": {
usernameTemplate: "",
newUserData: dbplugin.UsernameMetadata{
DisplayName: "displayname",
RoleName: "longrolename",
},
expectedRegex: "^v-displayn-longrole-[a-zA-Z0-9]{20}-[0-9]{10}$",
},
"unique template": {
usernameTemplate: "foo-bar",
newUserData: dbplugin.UsernameMetadata{
DisplayName: "displayname",
RoleName: "longrolename",
},
expectedRegex: "^foo-bar$",
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
initReq := dbplugin.InitializeRequest{
Config: map[string]interface{}{
"connection_url": connURL,
"username_template": test.usernameTemplate,
"auth_type": connutil.AuthTypeGCPIAM,
"service_account_json": credStr,
},
VerifyConnection: true,
}
db := new()
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
_, err := db.Initialize(ctx, initReq)
require.NoError(t, err)
newUserReq := dbplugin.NewUserRequest{
UsernameConfig: test.newUserData,
Statements: dbplugin.Statements{
Commands: []string{
`
CREATE ROLE "{{name}}" WITH
LOGIN
PASSWORD '{{password}}'
VALID UNTIL '{{expiration}}';
GRANT SELECT ON ALL TABLES IN SCHEMA public TO "{{name}}";`,
},
},
Password: "myReally-S3curePassword",
Expiration: time.Now().Add(1 * time.Hour),
}
ctx, cancel = context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
newUserResp, err := db.NewUser(ctx, newUserReq)
require.NoError(t, err)
require.Regexp(t, test.expectedRegex, newUserResp.Username)
})
}
}
// This is a long-running integration test which tests the functionality of Postgres's multi-host
// connection strings. It uses two Postgres containers preconfigured with Replication Manager
// provided by Bitnami. This test currently does not run in CI and must be run manually. This is