mirror of
				https://github.com/optim-enterprises-bv/vault.git
				synced 2025-10-31 18:48:08 +00:00 
			
		
		
		
	plugins/database: Allow both {{name}} and {{username}} in MySQL & Postgres (#8240)
* Allow {{name}} or {{username}} in psql templates
* Fix default rotation bug; allow {{user}} and {{username}}
			
			
This commit is contained in:
		| @@ -26,10 +26,6 @@ ALTER ROLE "{{name}}" VALID UNTIL '{{expiration}}'; | ||||
| ` | ||||
| 	defaultPostgresRotateRootCredentialsSQL = ` | ||||
| ALTER ROLE "{{username}}" WITH PASSWORD '{{password}}'; | ||||
| ` | ||||
|  | ||||
| 	defaultPostgresRotateCredentialsSQL = ` | ||||
| ALTER ROLE "{{name}}" WITH PASSWORD '{{password}}'; | ||||
| ` | ||||
| ) | ||||
|  | ||||
| @@ -149,6 +145,7 @@ func (p *PostgreSQL) SetCredentials(ctx context.Context, statements dbplugin.Sta | ||||
|  | ||||
| 			m := map[string]string{ | ||||
| 				"name":     staticUser.Username, | ||||
| 				"username": staticUser.Username, | ||||
| 				"password": password, | ||||
| 			} | ||||
| 			if err := dbtxn.ExecuteTxQuery(ctx, tx, m, query); err != nil { | ||||
| @@ -217,6 +214,7 @@ func (p *PostgreSQL) CreateUser(ctx context.Context, statements dbplugin.Stateme | ||||
|  | ||||
| 			m := map[string]string{ | ||||
| 				"name":       username, | ||||
| 				"username":   username, | ||||
| 				"password":   password, | ||||
| 				"expiration": expirationStr, | ||||
| 			} | ||||
| @@ -272,6 +270,7 @@ func (p *PostgreSQL) RenewUser(ctx context.Context, statements dbplugin.Statemen | ||||
|  | ||||
| 			m := map[string]string{ | ||||
| 				"name":       username, | ||||
| 				"username":   username, | ||||
| 				"expiration": expirationStr, | ||||
| 			} | ||||
| 			if err := dbtxn.ExecuteTxQuery(ctx, tx, m, query); err != nil { | ||||
| @@ -319,7 +318,8 @@ func (p *PostgreSQL) customRevokeUser(ctx context.Context, username string, revo | ||||
| 			} | ||||
|  | ||||
| 			m := map[string]string{ | ||||
| 				"name": username, | ||||
| 				"name":     username, | ||||
| 				"username": username, | ||||
| 			} | ||||
| 			if err := dbtxn.ExecuteTxQuery(ctx, tx, m, query); err != nil { | ||||
| 				return err | ||||
| @@ -479,6 +479,7 @@ func (p *PostgreSQL) RotateRootCredentials(ctx context.Context, statements []str | ||||
| 				continue | ||||
| 			} | ||||
| 			m := map[string]string{ | ||||
| 				"name":     p.Username, | ||||
| 				"username": p.Username, | ||||
| 				"password": password, | ||||
| 			} | ||||
|   | ||||
| @@ -6,7 +6,6 @@ import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"strings" | ||||
| 	"sync" | ||||
| 	"testing" | ||||
| 	"time" | ||||
|  | ||||
| @@ -17,10 +16,6 @@ import ( | ||||
| 	"github.com/ory/dockertest" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	testPostgresImagePull sync.Once | ||||
| ) | ||||
|  | ||||
| func preparePostgresTestContainer(t *testing.T) (cleanup func(), retURL string) { | ||||
| 	if os.Getenv("PG_URL") != "" { | ||||
| 		return func() {}, os.Getenv("PG_URL") | ||||
| @@ -97,7 +92,73 @@ func TestPostgreSQL_Initialize(t *testing.T) { | ||||
|  | ||||
| } | ||||
|  | ||||
| func TestPostgreSQL_CreateUser_missingArgs(t *testing.T) { | ||||
| 	db := new() | ||||
|  | ||||
| 	usernameConfig := dbplugin.UsernameConfig{ | ||||
| 		DisplayName: "test", | ||||
| 		RoleName:    "test", | ||||
| 	} | ||||
|  | ||||
| 	username, password, err := db.CreateUser(context.Background(), dbplugin.Statements{}, usernameConfig, time.Now().Add(time.Minute)) | ||||
| 	if err == nil { | ||||
| 		t.Fatalf("expected err, got nil") | ||||
| 	} | ||||
| 	if username != "" { | ||||
| 		t.Fatalf("expected empty username, got [%s]", username) | ||||
| 	} | ||||
| 	if password != "" { | ||||
| 		t.Fatalf("expected empty password, got [%s]", password) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestPostgreSQL_CreateUser(t *testing.T) { | ||||
| 	type testCase struct { | ||||
| 		createStmts []string | ||||
| 	} | ||||
|  | ||||
| 	tests := map[string]testCase{ | ||||
| 		"admin name": { | ||||
| 			createStmts: []string{` | ||||
| 				CREATE ROLE "{{name}}" WITH | ||||
| 				  LOGIN | ||||
| 				  PASSWORD '{{password}}' | ||||
| 				  VALID UNTIL '{{expiration}}'; | ||||
| 				GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "{{name}}";`, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"admin username": { | ||||
| 			createStmts: []string{` | ||||
| 				CREATE ROLE "{{username}}" WITH | ||||
| 				  LOGIN | ||||
| 				  PASSWORD '{{password}}' | ||||
| 				  VALID UNTIL '{{expiration}}'; | ||||
| 				GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "{{username}}";`, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"read only name": { | ||||
| 			createStmts: []string{` | ||||
| 				CREATE ROLE "{{name}}" WITH | ||||
| 				  LOGIN | ||||
| 				  PASSWORD '{{password}}' | ||||
| 				  VALID UNTIL '{{expiration}}'; | ||||
| 				GRANT SELECT ON ALL TABLES IN SCHEMA public TO "{{name}}"; | ||||
| 				GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO "{{name}}";`, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"read only username": { | ||||
| 			createStmts: []string{` | ||||
| 				CREATE ROLE "{{username}}" WITH | ||||
| 				  LOGIN | ||||
| 				  PASSWORD '{{password}}' | ||||
| 				  VALID UNTIL '{{expiration}}'; | ||||
| 				GRANT SELECT ON ALL TABLES IN SCHEMA public TO "{{username}}"; | ||||
| 				GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO "{{username}}";`, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	// Shared test container for speed - there should not be any overlap between the tests | ||||
| 	cleanup, connURL := preparePostgresTestContainer(t) | ||||
| 	defer cleanup() | ||||
|  | ||||
| @@ -111,45 +172,60 @@ func TestPostgreSQL_CreateUser(t *testing.T) { | ||||
| 		t.Fatalf("err: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	usernameConfig := dbplugin.UsernameConfig{ | ||||
| 		DisplayName: "test", | ||||
| 		RoleName:    "test", | ||||
| 	} | ||||
| 	for name, test := range tests { | ||||
| 		t.Run(name, func(t *testing.T) { | ||||
| 			usernameConfig := dbplugin.UsernameConfig{ | ||||
| 				DisplayName: "test", | ||||
| 				RoleName:    "test", | ||||
| 			} | ||||
|  | ||||
| 	// Test with no configured Creation Statement | ||||
| 	_, _, err = db.CreateUser(context.Background(), dbplugin.Statements{}, usernameConfig, time.Now().Add(time.Minute)) | ||||
| 	if err == nil { | ||||
| 		t.Fatal("Expected error when no creation statement is provided") | ||||
| 	} | ||||
| 			statements := dbplugin.Statements{ | ||||
| 				Creation: test.createStmts, | ||||
| 			} | ||||
|  | ||||
| 	statements := dbplugin.Statements{ | ||||
| 		Creation: []string{testPostgresRole}, | ||||
| 	} | ||||
| 			// Give a timeout just in case the test decides to be problematic | ||||
| 			ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) | ||||
| 			defer cancel() | ||||
|  | ||||
| 	username, password, err := db.CreateUser(context.Background(), statements, usernameConfig, time.Now().Add(time.Minute)) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %s", err) | ||||
| 	} | ||||
| 			username, password, err := db.CreateUser(ctx, statements, usernameConfig, time.Now().Add(time.Minute)) | ||||
| 			if err != nil { | ||||
| 				t.Fatalf("err: %s", err) | ||||
| 			} | ||||
|  | ||||
| 	if err = testCredsExist(t, connURL, username, password); err != nil { | ||||
| 		t.Fatalf("Could not connect with new credentials: %s", err) | ||||
| 	} | ||||
| 			if err = testCredsExist(t, connURL, username, password); err != nil { | ||||
| 				t.Fatalf("Could not connect with new credentials: %s", err) | ||||
| 			} | ||||
|  | ||||
| 	statements.Creation = []string{testPostgresReadOnlyRole} | ||||
| 	username, password, err = db.CreateUser(context.Background(), statements, usernameConfig, time.Now().Add(time.Minute)) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %s", err) | ||||
| 	} | ||||
| 			// Ensure that the role doesn't expire immediately | ||||
| 			time.Sleep(2 * time.Second) | ||||
|  | ||||
| 	// Sleep to make sure we haven't expired if granularity is only down to the second | ||||
| 	time.Sleep(2 * time.Second) | ||||
|  | ||||
| 	if err = testCredsExist(t, connURL, username, password); err != nil { | ||||
| 		t.Fatalf("Could not connect with new credentials: %s", err) | ||||
| 			if err = testCredsExist(t, connURL, username, password); err != nil { | ||||
| 				t.Fatalf("Could not connect with new credentials: %s", err) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestPostgreSQL_RenewUser(t *testing.T) { | ||||
| 	type testCase struct { | ||||
| 		renewalStmts []string | ||||
| 	} | ||||
|  | ||||
| 	tests := map[string]testCase{ | ||||
| 		"empty renewal statements": { | ||||
| 			renewalStmts: nil, | ||||
| 		}, | ||||
| 		"default renewal name": { | ||||
| 			renewalStmts: []string{defaultPostgresRenewSQL}, | ||||
| 		}, | ||||
| 		"default renewal username": { | ||||
| 			renewalStmts: []string{` | ||||
| 				ALTER ROLE "{{username}}" VALID UNTIL '{{expiration}}';`, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	// Shared test container for speed - there should not be any overlap between the tests | ||||
| 	cleanup, connURL := preparePostgresTestContainer(t) | ||||
| 	defer cleanup() | ||||
|  | ||||
| @@ -158,105 +234,146 @@ func TestPostgreSQL_RenewUser(t *testing.T) { | ||||
| 	} | ||||
|  | ||||
| 	db := new() | ||||
| 	_, err := db.Init(context.Background(), connectionDetails, true) | ||||
|  | ||||
| 	// Give a timeout just in case the test decides to be problematic | ||||
| 	initCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) | ||||
| 	defer cancel() | ||||
|  | ||||
| 	_, err := db.Init(initCtx, connectionDetails, true) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	statements := dbplugin.Statements{ | ||||
| 		Creation: []string{testPostgresRole}, | ||||
| 	for name, test := range tests { | ||||
| 		t.Run(name, func(t *testing.T) { | ||||
| 			statements := dbplugin.Statements{ | ||||
| 				Creation: []string{createAdminUser}, | ||||
| 				Renewal:  test.renewalStmts, | ||||
| 			} | ||||
|  | ||||
| 			usernameConfig := dbplugin.UsernameConfig{ | ||||
| 				DisplayName: "test", | ||||
| 				RoleName:    "test", | ||||
| 			} | ||||
|  | ||||
| 			// Give a timeout just in case the test decides to be problematic | ||||
| 			ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) | ||||
| 			defer cancel() | ||||
|  | ||||
| 			username, password, err := db.CreateUser(ctx, statements, usernameConfig, time.Now().Add(2*time.Second)) | ||||
| 			if err != nil { | ||||
| 				t.Fatalf("err: %s", err) | ||||
| 			} | ||||
|  | ||||
| 			if err = testCredsExist(t, connURL, username, password); err != nil { | ||||
| 				t.Fatalf("Could not connect with new credentials: %s", err) | ||||
| 			} | ||||
|  | ||||
| 			err = db.RenewUser(ctx, statements, username, time.Now().Add(time.Minute)) | ||||
| 			if err != nil { | ||||
| 				t.Fatalf("err: %s", err) | ||||
| 			} | ||||
|  | ||||
| 			// Sleep longer than the initial expiration time | ||||
| 			time.Sleep(2 * time.Second) | ||||
|  | ||||
| 			if err = testCredsExist(t, connURL, username, password); err != nil { | ||||
| 				t.Fatalf("Could not connect with new credentials: %s", err) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
|  | ||||
| 	usernameConfig := dbplugin.UsernameConfig{ | ||||
| 		DisplayName: "test", | ||||
| 		RoleName:    "test", | ||||
| 	} | ||||
|  | ||||
| 	username, password, err := db.CreateUser(context.Background(), statements, usernameConfig, time.Now().Add(2*time.Second)) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	if err = testCredsExist(t, connURL, username, password); err != nil { | ||||
| 		t.Fatalf("Could not connect with new credentials: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	err = db.RenewUser(context.Background(), statements, username, time.Now().Add(time.Minute)) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	// Sleep longer than the initial expiration time | ||||
| 	time.Sleep(2 * time.Second) | ||||
|  | ||||
| 	if err = testCredsExist(t, connURL, username, password); err != nil { | ||||
| 		t.Fatalf("Could not connect with new credentials: %s", err) | ||||
| 	} | ||||
| 	statements.Renewal = []string{defaultPostgresRenewSQL} | ||||
| 	username, password, err = db.CreateUser(context.Background(), statements, usernameConfig, time.Now().Add(2*time.Second)) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	if err = testCredsExist(t, connURL, username, password); err != nil { | ||||
| 		t.Fatalf("Could not connect with new credentials: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	err = db.RenewUser(context.Background(), statements, username, time.Now().Add(time.Minute)) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	// Sleep longer than the initial expiration time | ||||
| 	time.Sleep(2 * time.Second) | ||||
|  | ||||
| 	if err = testCredsExist(t, connURL, username, password); err != nil { | ||||
| 		t.Fatalf("Could not connect with new credentials: %s", err) | ||||
| 	} | ||||
|  | ||||
| } | ||||
|  | ||||
| func TestPostgreSQL_RotateRootCredentials(t *testing.T) { | ||||
| 	cleanup, connURL := preparePostgresTestContainer(t) | ||||
| 	defer cleanup() | ||||
|  | ||||
| 	connURL = strings.Replace(connURL, "postgres:secret", `{{username}}:{{password}}`, -1) | ||||
|  | ||||
| 	connectionDetails := map[string]interface{}{ | ||||
| 		"connection_url":       connURL, | ||||
| 		"max_open_connections": 5, | ||||
| 		"username":             "postgres", | ||||
| 		"password":             "secret", | ||||
| 	type testCase struct { | ||||
| 		statements []string | ||||
| 	} | ||||
|  | ||||
| 	db := new() | ||||
|  | ||||
| 	connProducer := db.SQLConnectionProducer | ||||
|  | ||||
| 	_, err := db.Init(context.Background(), connectionDetails, true) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %s", err) | ||||
| 	tests := map[string]testCase{ | ||||
| 		"empty statements": { | ||||
| 			statements: nil, | ||||
| 		}, | ||||
| 		"default name": { | ||||
| 			statements: []string{` | ||||
| 				ALTER ROLE "{{name}}" WITH PASSWORD '{{password}}';`, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"default username": { | ||||
| 			statements: []string{defaultPostgresRotateRootCredentialsSQL}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	if !connProducer.Initialized { | ||||
| 		t.Fatal("Database should be initialized") | ||||
| 	} | ||||
| 	for name, test := range tests { | ||||
| 		t.Run(name, func(t *testing.T) { | ||||
| 			cleanup, connURL := preparePostgresTestContainer(t) | ||||
| 			defer cleanup() | ||||
|  | ||||
| 	newConf, err := db.RotateRootCredentials(context.Background(), nil) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %v", err) | ||||
| 	} | ||||
| 	if newConf["password"] == "secret" { | ||||
| 		t.Fatal("password was not updated") | ||||
| 	} | ||||
| 			connURL = strings.Replace(connURL, "postgres:secret", `{{username}}:{{password}}`, -1) | ||||
|  | ||||
| 	err = db.Close() | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %s", err) | ||||
| 			connectionDetails := map[string]interface{}{ | ||||
| 				"connection_url":       connURL, | ||||
| 				"max_open_connections": 5, | ||||
| 				"username":             "postgres", | ||||
| 				"password":             "secret", | ||||
| 			} | ||||
|  | ||||
| 			db := new() | ||||
|  | ||||
| 			connProducer := db.SQLConnectionProducer | ||||
|  | ||||
| 			// Give a timeout just in case the test decides to be problematic | ||||
| 			ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) | ||||
| 			defer cancel() | ||||
|  | ||||
| 			_, err := db.Init(ctx, connectionDetails, true) | ||||
| 			if err != nil { | ||||
| 				t.Fatalf("err: %s", err) | ||||
| 			} | ||||
|  | ||||
| 			if !connProducer.Initialized { | ||||
| 				t.Fatal("Database should be initialized") | ||||
| 			} | ||||
|  | ||||
| 			newConf, err := db.RotateRootCredentials(ctx, test.statements) | ||||
| 			if err != nil { | ||||
| 				t.Fatalf("err: %v", err) | ||||
| 			} | ||||
| 			if newConf["password"] == "secret" { | ||||
| 				t.Fatal("password was not updated") | ||||
| 			} | ||||
|  | ||||
| 			err = db.Close() | ||||
| 			if err != nil { | ||||
| 				t.Fatalf("failed to close: %s", err) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestPostgreSQL_RevokeUser(t *testing.T) { | ||||
| 	type testCase struct { | ||||
| 		revokeStmts []string | ||||
| 	} | ||||
|  | ||||
| 	tests := map[string]testCase{ | ||||
| 		"empty statements": { | ||||
| 			revokeStmts: nil, | ||||
| 		}, | ||||
| 		"explicit default name": { | ||||
| 			revokeStmts: []string{defaultPostgresRevocationSQL}, | ||||
| 		}, | ||||
| 		"explicit default username": { | ||||
| 			revokeStmts: []string{` | ||||
| 				REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM "{{username}}"; | ||||
| 				REVOKE ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public FROM "{{username}}"; | ||||
| 				REVOKE USAGE ON SCHEMA public FROM "{{username}}"; | ||||
| 				 | ||||
| 				DROP ROLE IF EXISTS "{{username}}";`, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	// Shared test container for speed - there should not be any overlap between the tests | ||||
| 	cleanup, connURL := preparePostgresTestContainer(t) | ||||
| 	defer cleanup() | ||||
|  | ||||
| @@ -265,121 +382,183 @@ func TestPostgreSQL_RevokeUser(t *testing.T) { | ||||
| 	} | ||||
|  | ||||
| 	db := new() | ||||
| 	_, err := db.Init(context.Background(), connectionDetails, true) | ||||
|  | ||||
| 	// Give a timeout just in case the test decides to be problematic | ||||
| 	initCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) | ||||
| 	defer cancel() | ||||
|  | ||||
| 	_, err := db.Init(initCtx, connectionDetails, true) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	statements := dbplugin.Statements{ | ||||
| 		Creation: []string{testPostgresRole}, | ||||
| 	for name, test := range tests { | ||||
| 		t.Run(name, func(t *testing.T) { | ||||
| 			statements := dbplugin.Statements{ | ||||
| 				Creation:   []string{createAdminUser}, | ||||
| 				Revocation: test.revokeStmts, | ||||
| 			} | ||||
|  | ||||
| 			usernameConfig := dbplugin.UsernameConfig{ | ||||
| 				DisplayName: "test", | ||||
| 				RoleName:    "test", | ||||
| 			} | ||||
|  | ||||
| 			username, password, err := db.CreateUser(context.Background(), statements, usernameConfig, time.Now().Add(2*time.Second)) | ||||
| 			if err != nil { | ||||
| 				t.Fatalf("err: %s", err) | ||||
| 			} | ||||
|  | ||||
| 			if err = testCredsExist(t, connURL, username, password); err != nil { | ||||
| 				t.Fatalf("Could not connect with new credentials: %s", err) | ||||
| 			} | ||||
|  | ||||
| 			// Test default revoke statements | ||||
| 			err = db.RevokeUser(context.Background(), statements, username) | ||||
| 			if err != nil { | ||||
| 				t.Fatalf("err: %s", err) | ||||
| 			} | ||||
|  | ||||
| 			if err := testCredsExist(t, connURL, username, password); err == nil { | ||||
| 				t.Fatal("Credentials were not revoked") | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestPostgreSQL_SetCredentials_missingArgs(t *testing.T) { | ||||
| 	type testCase struct { | ||||
| 		statements dbplugin.Statements | ||||
| 		userConfig dbplugin.StaticUserConfig | ||||
| 	} | ||||
|  | ||||
| 	usernameConfig := dbplugin.UsernameConfig{ | ||||
| 		DisplayName: "test", | ||||
| 		RoleName:    "test", | ||||
| 	tests := map[string]testCase{ | ||||
| 		"empty rotation statements": { | ||||
| 			statements: dbplugin.Statements{ | ||||
| 				Rotation: nil, | ||||
| 			}, | ||||
| 			userConfig: dbplugin.StaticUserConfig{ | ||||
| 				Username: "testuser", | ||||
| 				Password: "password", | ||||
| 			}, | ||||
| 		}, | ||||
| 		"empty username": { | ||||
| 			statements: dbplugin.Statements{ | ||||
| 				Rotation: []string{` | ||||
| 					ALTER ROLE "{{name}}" WITH PASSWORD '{{password}}';`, | ||||
| 				}, | ||||
| 			}, | ||||
| 			userConfig: dbplugin.StaticUserConfig{ | ||||
| 				Username: "", | ||||
| 				Password: "password", | ||||
| 			}, | ||||
| 		}, | ||||
| 		"empty password": { | ||||
| 			statements: dbplugin.Statements{ | ||||
| 				Rotation: []string{` | ||||
| 					ALTER ROLE "{{name}}" WITH PASSWORD '{{password}}';`, | ||||
| 				}, | ||||
| 			}, | ||||
| 			userConfig: dbplugin.StaticUserConfig{ | ||||
| 				Username: "testuser", | ||||
| 				Password: "", | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	username, password, err := db.CreateUser(context.Background(), statements, usernameConfig, time.Now().Add(2*time.Second)) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %s", err) | ||||
| 	} | ||||
| 	for name, test := range tests { | ||||
| 		t.Run(name, func(t *testing.T) { | ||||
| 			db := new() | ||||
|  | ||||
| 	if err = testCredsExist(t, connURL, username, password); err != nil { | ||||
| 		t.Fatalf("Could not connect with new credentials: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	// Test default revoke statements | ||||
| 	err = db.RevokeUser(context.Background(), statements, username) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	if err := testCredsExist(t, connURL, username, password); err == nil { | ||||
| 		t.Fatal("Credentials were not revoked") | ||||
| 	} | ||||
|  | ||||
| 	username, password, err = db.CreateUser(context.Background(), statements, usernameConfig, time.Now().Add(2*time.Second)) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	if err = testCredsExist(t, connURL, username, password); err != nil { | ||||
| 		t.Fatalf("Could not connect with new credentials: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	// Test custom revoke statements | ||||
| 	statements.Revocation = []string{defaultPostgresRevocationSQL} | ||||
| 	err = db.RevokeUser(context.Background(), statements, username) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %s", err) | ||||
| 	} | ||||
|  | ||||
| 	if err := testCredsExist(t, connURL, username, password); err == nil { | ||||
| 		t.Fatal("Credentials were not revoked") | ||||
| 			username, password, err := db.SetCredentials(context.Background(), test.statements, test.userConfig) | ||||
| 			if err == nil { | ||||
| 				t.Fatalf("expected err, got nil") | ||||
| 			} | ||||
| 			if username != "" { | ||||
| 				t.Fatalf("expected empty username, got [%s]", username) | ||||
| 			} | ||||
| 			if password != "" { | ||||
| 				t.Fatalf("expected empty password, got [%s]", password) | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestPostgresSQL_SetCredentials(t *testing.T) { | ||||
| 	cleanup, connURL := preparePostgresTestContainer(t) | ||||
| 	defer cleanup() | ||||
|  | ||||
| 	// create the database user | ||||
| 	dbUser := "vaultstatictest" | ||||
| 	createTestPGUser(t, connURL, dbUser, "password", testRoleStaticCreate) | ||||
|  | ||||
| 	connectionDetails := map[string]interface{}{ | ||||
| 		"connection_url": connURL, | ||||
| 	type testCase struct { | ||||
| 		rotationStmts []string | ||||
| 	} | ||||
|  | ||||
| 	db := new() | ||||
| 	_, err := db.Init(context.Background(), connectionDetails, true) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %s", err) | ||||
| 	tests := map[string]testCase{ | ||||
| 		"name rotation": { | ||||
| 			rotationStmts: []string{` | ||||
| 				ALTER ROLE "{{name}}" WITH PASSWORD '{{password}}';`, | ||||
| 			}, | ||||
| 		}, | ||||
| 		"username rotation": { | ||||
| 			rotationStmts: []string{` | ||||
| 				ALTER ROLE "{{username}}" WITH PASSWORD '{{password}}';`, | ||||
| 			}, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	password, err := db.GenerateCredentials(context.Background()) | ||||
| 	if err != nil { | ||||
| 		t.Fatal(err) | ||||
| 	} | ||||
| 	for name, test := range tests { | ||||
| 		t.Run(name, func(t *testing.T) { | ||||
| 			// Shared test container for speed - there should not be any overlap between the tests | ||||
| 			cleanup, connURL := preparePostgresTestContainer(t) | ||||
| 			defer cleanup() | ||||
|  | ||||
| 	usernameConfig := dbplugin.StaticUserConfig{ | ||||
| 		Username: dbUser, | ||||
| 		Password: password, | ||||
| 	} | ||||
| 			// create the database user | ||||
| 			dbUser := "vaultstatictest" | ||||
| 			initPassword := "password" | ||||
| 			createTestPGUser(t, connURL, dbUser, initPassword, testRoleStaticCreate) | ||||
|  | ||||
| 	// Test with no configured Rotation Statement | ||||
| 	username, password, err := db.SetCredentials(context.Background(), dbplugin.Statements{}, usernameConfig) | ||||
| 	if err == nil { | ||||
| 		t.Fatalf("err: %s", err) | ||||
| 	} | ||||
| 			connectionDetails := map[string]interface{}{ | ||||
| 				"connection_url": connURL, | ||||
| 			} | ||||
|  | ||||
| 	statements := dbplugin.Statements{ | ||||
| 		Rotation: []string{testPostgresStaticRoleRotate}, | ||||
| 	} | ||||
| 	// User should not exist, make sure we can create | ||||
| 	username, password, err = db.SetCredentials(context.Background(), statements, usernameConfig) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %s", err) | ||||
| 	} | ||||
| 			db := new() | ||||
|  | ||||
| 	if err := testCredsExist(t, connURL, username, password); err != nil { | ||||
| 		t.Fatalf("Could not connect with new credentials: %s", err) | ||||
| 	} | ||||
| 			// Give a timeout just in case the test decides to be problematic | ||||
| 			ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) | ||||
| 			defer cancel() | ||||
|  | ||||
| 	// call SetCredentials again, password will change | ||||
| 	newPassword, _ := db.GenerateCredentials(context.Background()) | ||||
| 	usernameConfig.Password = newPassword | ||||
| 	username, password, err = db.SetCredentials(context.Background(), statements, usernameConfig) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("err: %s", err) | ||||
| 	} | ||||
| 			_, err := db.Init(ctx, connectionDetails, true) | ||||
| 			if err != nil { | ||||
| 				t.Fatalf("err: %s", err) | ||||
| 			} | ||||
|  | ||||
| 	if password != newPassword { | ||||
| 		t.Fatal("passwords should have changed") | ||||
| 	} | ||||
| 			statements := dbplugin.Statements{ | ||||
| 				Rotation: test.rotationStmts, | ||||
| 			} | ||||
|  | ||||
| 	if err := testCredsExist(t, connURL, username, password); err != nil { | ||||
| 		t.Fatalf("Could not connect with new credentials: %s", err) | ||||
| 			password, err := db.GenerateCredentials(context.Background()) | ||||
| 			if err != nil { | ||||
| 				t.Fatal(err) | ||||
| 			} | ||||
|  | ||||
| 			usernameConfig := dbplugin.StaticUserConfig{ | ||||
| 				Username: dbUser, | ||||
| 				Password: password, | ||||
| 			} | ||||
|  | ||||
| 			if err := testCredsExist(t, connURL, dbUser, initPassword); err != nil { | ||||
| 				t.Fatalf("Could not connect with initial credentials: %s", err) | ||||
| 			} | ||||
|  | ||||
| 			username, password, err := db.SetCredentials(ctx, statements, usernameConfig) | ||||
| 			if err != nil { | ||||
| 				t.Fatalf("err: %s", err) | ||||
| 			} | ||||
|  | ||||
| 			if err := testCredsExist(t, connURL, username, password); err != nil { | ||||
| 				t.Fatalf("Could not connect with new credentials: %s", err) | ||||
| 			} | ||||
|  | ||||
| 			if err := testCredsExist(t, connURL, username, initPassword); err == nil { | ||||
| 				t.Fatalf("Should not be able to connect with initial credentials") | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -395,7 +574,7 @@ func testCredsExist(t testing.TB, connURL, username, password string) error { | ||||
| 	return db.Ping() | ||||
| } | ||||
|  | ||||
| const testPostgresRole = ` | ||||
| const createAdminUser = ` | ||||
| CREATE ROLE "{{name}}" WITH | ||||
|   LOGIN | ||||
|   PASSWORD '{{password}}' | ||||
| @@ -403,37 +582,6 @@ CREATE ROLE "{{name}}" WITH | ||||
| GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "{{name}}"; | ||||
| ` | ||||
|  | ||||
| const testPostgresReadOnlyRole = ` | ||||
| CREATE ROLE "{{name}}" WITH | ||||
|   LOGIN | ||||
|   PASSWORD '{{password}}' | ||||
|   VALID UNTIL '{{expiration}}'; | ||||
| GRANT SELECT ON ALL TABLES IN SCHEMA public TO "{{name}}"; | ||||
| GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO "{{name}}"; | ||||
| ` | ||||
|  | ||||
| const testPostgresBlockStatementRole = ` | ||||
| DO $$ | ||||
| BEGIN | ||||
|    IF NOT EXISTS (SELECT * FROM pg_catalog.pg_roles WHERE rolname='foo-role') THEN | ||||
|       CREATE ROLE "foo-role"; | ||||
|       CREATE SCHEMA IF NOT EXISTS foo AUTHORIZATION "foo-role"; | ||||
|       ALTER ROLE "foo-role" SET search_path = foo; | ||||
|       GRANT TEMPORARY ON DATABASE "postgres" TO "foo-role"; | ||||
|       GRANT ALL PRIVILEGES ON SCHEMA foo TO "foo-role"; | ||||
|       GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA foo TO "foo-role"; | ||||
|       GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA foo TO "foo-role"; | ||||
|       GRANT ALL PRIVILEGES ON ALL FUNCTIONS IN SCHEMA foo TO "foo-role"; | ||||
|    END IF; | ||||
| END | ||||
| $$ | ||||
|  | ||||
| CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; | ||||
| GRANT "foo-role" TO "{{name}}"; | ||||
| ALTER ROLE "{{name}}" SET search_path = foo; | ||||
| GRANT CONNECT ON DATABASE "postgres" TO "{{name}}"; | ||||
| ` | ||||
|  | ||||
| var testPostgresBlockStatementRoleSlice = []string{ | ||||
| 	` | ||||
| DO $$ | ||||
| @@ -465,27 +613,12 @@ REVOKE USAGE ON SCHEMA public FROM "{{name}}"; | ||||
| DROP ROLE IF EXISTS "{{name}}"; | ||||
| ` | ||||
|  | ||||
| const testPostgresStaticRole = ` | ||||
| CREATE ROLE "{{name}}" WITH | ||||
|   LOGIN | ||||
|   PASSWORD '{{password}}'; | ||||
| GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "{{name}}"; | ||||
| ` | ||||
|  | ||||
| const testRoleStaticCreate = ` | ||||
| CREATE ROLE "{{name}}" WITH | ||||
|   LOGIN | ||||
|   PASSWORD '{{password}}'; | ||||
| ` | ||||
|  | ||||
| const testPostgresStaticRoleRotate = ` | ||||
| ALTER ROLE "{{name}}" WITH PASSWORD '{{password}}'; | ||||
| ` | ||||
|  | ||||
| const testPostgresStaticRoleGrant = ` | ||||
| GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "{{name}}"; | ||||
| ` | ||||
|  | ||||
| // This is a copy of a test helper method also found in | ||||
| // builtin/logical/database/rotation_test.go , and should be moved into a shared | ||||
| // helper file in the future. | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Michael Golowka
					Michael Golowka