Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[v14] Database Automatic User Provisioning support for Redshift (#34126)
* Database Automatic User Provisioning support for Redshift (#33307) * Database Automatic User Provisioning support for Redshift * capitalize sql script * DeleteUser to fallback to deactivate. * add TPxxx code to RAISE messages * fix missing space in scripts * feat(postgres): support auto-provisioned user deletion Redshift (#34006) --------- Co-authored-by: Gabriel Corado <gabriel.oliveira@goteleport.com>
- Loading branch information
1 parent
b36f0a9
commit 38cc706
Showing
9 changed files
with
262 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
CREATE OR REPLACE PROCEDURE teleport_activate_user(username varchar, roles text) | ||
LANGUAGE plpgsql | ||
AS $$ | ||
DECLARE | ||
roles_length integer; | ||
cur_roles_length integer; | ||
BEGIN | ||
roles_length := JSON_ARRAY_LENGTH(roles); | ||
|
||
-- If the user already exists and was provisioned by Teleport, reactivate | ||
-- it, otherwise provision a new one. | ||
IF EXISTS (SELECT user_id FROM svv_user_grants WHERE user_name = username AND admin_option = false AND role_name = 'teleport-auto-user') THEN | ||
-- If the user has active connections, make sure the provided roles | ||
-- match what the user currently has. | ||
IF EXISTS (SELECT user_name FROM stv_sessions WHERE user_name = CONCAT('IAM:', username)) THEN | ||
SELECT INTO cur_roles_length COUNT(role_name) FROM svv_user_grants WHERE user_name = username AND admin_option=false AND role_name != 'teleport-auto-user'; | ||
IF roles_length != cur_roles_length THEN | ||
RAISE EXCEPTION 'TP002: User has active connections and roles have changed'; | ||
END IF; | ||
FOR i IN 0..roles_length-1 LOOP | ||
IF NOT EXISTS (SELECT role_name FROM svv_user_grants WHERE user_name = username AND admin_option=false AND role_name = JSON_EXTRACT_ARRAY_ELEMENT_TEXT(roles,i)) THEN | ||
RAISE EXCEPTION 'TP002: User has active connections and roles have changed'; | ||
END IF; | ||
END LOOP; | ||
RETURN; | ||
END IF; | ||
-- Otherwise reactivate the user, but first strip it of all roles to | ||
-- account for scenarios with left-over roles if database agent crashed | ||
-- and failed to cleanup upon session termination. | ||
CALL teleport_deactivate_user(username); | ||
EXECUTE 'ALTER USER ' || QUOTE_IDENT(username) || ' CONNECTION LIMIT UNLIMITED'; | ||
ELSE | ||
EXECUTE 'CREATE USER ' || QUOTE_IDENT(username) || ' WITH PASSWORD DISABLE'; | ||
EXECUTE 'GRANT ROLE "teleport-auto-user" TO ' || QUOTE_IDENT(username); | ||
END IF; | ||
-- Assign all roles to the created/activated user. | ||
FOR i in 0..roles_length-1 LOOP | ||
EXECUTE 'GRANT ROLE ' || QUOTE_IDENT(JSON_EXTRACT_ARRAY_ELEMENT_TEXT(roles,i)) || ' TO ' || QUOTE_IDENT(username); | ||
END LOOP; | ||
END;$$; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
CREATE OR REPLACE PROCEDURE teleport_deactivate_user(username varchar) | ||
LANGUAGE plpgsql | ||
AS $$ | ||
DECLARE | ||
rec record; | ||
BEGIN | ||
-- Only deactivate if the user doesn't have other active sessions. | ||
-- Update to pg_stat_activity is delayed for a few hundred ms. Use | ||
-- stv_sessions instead. | ||
IF EXISTS (SELECT user_name FROM stv_sessions WHERE user_name = CONCAT('IAM:', username)) THEN | ||
RAISE EXCEPTION 'TP000: User has active connections'; | ||
ELSE | ||
-- Revoke all role memberships except teleport-auto-user. | ||
FOR rec IN select role_name FROM svv_user_grants WHERE user_name = username AND admin_option = false AND role_name != 'teleport-auto-user' LOOP | ||
EXECUTE 'REVOKE ROLE ' || QUOTE_IDENT(rec.role_name) || ' FROM ' || QUOTE_IDENT(username); | ||
END LOOP; | ||
-- Disable ability to login for the user. | ||
EXECUTE 'ALTER USER ' || QUOTE_IDENT(username) || ' WITH CONNECTION LIMIT 0'; | ||
END IF; | ||
END;$$; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
CREATE OR REPLACE PROCEDURE teleport_delete_user(username varchar) | ||
LANGUAGE plpgsql | ||
AS $$ | ||
BEGIN | ||
-- Only drop if the user doesn't have other active sessions. | ||
IF EXISTS (SELECT usename FROM pg_stat_activity WHERE usename = username) THEN | ||
RAISE NOTICE 'User has active connections'; | ||
ELSE | ||
BEGIN | ||
EXECUTE 'DROP USER ' || QUOTE_IDENT(username); | ||
EXCEPTION WHEN OTHERS THEN | ||
-- Redshift only support OTHERS as exception condition, so we handle | ||
-- any error that might happen. | ||
|
||
-- Drop user/role will fail if user has dependent objects. | ||
-- In this scenario, fallback into disabling the user. | ||
CALL teleport_deactivate_user(username); | ||
END; | ||
END IF; | ||
END;$$; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
/* | ||
Copyright 2023 Gravitational, Inc. | ||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
http://www.apache.org/licenses/LICENSE-2.0 | ||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
package postgres | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/gravitational/teleport/api/types" | ||
"github.com/gravitational/teleport/lib/defaults" | ||
"github.com/gravitational/teleport/lib/srv/db/common" | ||
) | ||
|
||
func Test_prepareRoles(t *testing.T) { | ||
selfHostedDatabase, err := types.NewDatabaseV3(types.Metadata{ | ||
Name: "self-hosted", | ||
}, types.DatabaseSpecV3{ | ||
Protocol: defaults.ProtocolPostgres, | ||
URI: "localhost:5432", | ||
}) | ||
require.NoError(t, err) | ||
|
||
rdsDatabase, err := types.NewDatabaseV3(types.Metadata{ | ||
Name: "rds", | ||
}, types.DatabaseSpecV3{ | ||
Protocol: defaults.ProtocolPostgres, | ||
URI: "aurora-instance-1.abcdefghijklmnop.us-west-1.rds.amazonaws.com:5432", | ||
}) | ||
require.NoError(t, err) | ||
|
||
redshiftDatabase, err := types.NewDatabaseV3(types.Metadata{ | ||
Name: "redshift", | ||
}, types.DatabaseSpecV3{ | ||
Protocol: defaults.ProtocolPostgres, | ||
URI: "redshift-cluster-1.abcdefghijklmnop.us-east-1.redshift.amazonaws.com:5439", | ||
}) | ||
require.NoError(t, err) | ||
|
||
tests := []struct { | ||
inputDatabase types.Database | ||
expectRoles any | ||
}{ | ||
{ | ||
inputDatabase: selfHostedDatabase, | ||
expectRoles: []string{"role1", "role2"}, | ||
}, | ||
{ | ||
inputDatabase: rdsDatabase, | ||
expectRoles: []string{"role1", "role2", "rds_iam"}, | ||
}, | ||
{ | ||
inputDatabase: redshiftDatabase, | ||
expectRoles: `["role1","role2"]`, | ||
}, | ||
} | ||
|
||
for _, test := range tests { | ||
t.Run(test.inputDatabase.GetName(), func(t *testing.T) { | ||
sessionCtx := &common.Session{ | ||
Database: test.inputDatabase, | ||
DatabaseRoles: []string{"role1", "role2"}, | ||
} | ||
|
||
actualRoles, err := prepareRoles(sessionCtx) | ||
require.NoError(t, err) | ||
require.Equal(t, test.expectRoles, actualRoles) | ||
}) | ||
} | ||
} |