mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 02:28:09 +00:00 
			
		
		
		
	Add support for IAM Auth for Google CloudSQL DBs (#22445)
This commit is contained in:
		| @@ -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 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 kpcraig
					kpcraig