Skip to content

Commit 43c603a

Browse files
[CMCSMACD-4695] Add a function for syncing users and roles (#10)
This will be used instead of migrations for making sure that a database has the right users and roles.
1 parent b59bf13 commit 43c603a

1 file changed

Lines changed: 126 additions & 0 deletions

File tree

migrations/user.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package migrations
2+
3+
import (
4+
"database/sql"
5+
"fmt"
6+
"log"
7+
8+
"github.com/jmoiron/sqlx"
9+
"github.com/lib/pq"
10+
)
11+
12+
type PostgreSQLUser struct {
13+
Username string
14+
GrantRoles []string
15+
}
16+
17+
type UserAuthenticationType string
18+
19+
const (
20+
UserAuthenticationTypeIAM UserAuthenticationType = "iam"
21+
UserAuthenticationTypePassword UserAuthenticationType = "password"
22+
)
23+
24+
// Make sure that the given users exist in database cluster and have only the
25+
// role memberships specified. If authType is UserAuthenticationTypePassword,
26+
// set each user's password to its username. Otherwise remove each user's password
27+
// and also add the rds_iam role for each user.
28+
// All operations are done in a single transaction.
29+
func EnsureUsersWithRoles(db *sqlx.DB, users []PostgreSQLUser, authType UserAuthenticationType) error {
30+
tx, err := db.Begin()
31+
if err != nil {
32+
return fmt.Errorf("Error starting transaction: %w", err)
33+
}
34+
defer func() {
35+
err := tx.Rollback()
36+
if err != nil && err != sql.ErrTxDone {
37+
log.Printf("Error rolling back: %s", err)
38+
}
39+
}()
40+
41+
for _, user := range users {
42+
createUserSQL := fmt.Sprintf(`
43+
DO $$
44+
DECLARE
45+
username text := %s;
46+
BEGIN
47+
IF NOT EXISTS (
48+
SELECT FROM pg_catalog.pg_user WHERE usename = username
49+
) THEN
50+
EXECUTE format('CREATE USER %%I', username);
51+
END IF;
52+
END
53+
$$`, pq.QuoteLiteral(user.Username))
54+
_, err := tx.Exec(createUserSQL)
55+
if err != nil {
56+
return fmt.Errorf("Failed to create user %q: %w", user.Username, err)
57+
}
58+
59+
// Drop all existing roles
60+
dropRolesSQL := fmt.Sprintf(`
61+
DO $$
62+
DECLARE
63+
r RECORD;
64+
BEGIN
65+
FOR r IN
66+
SELECT roleid::regrole AS granted_role
67+
FROM pg_catalog.pg_auth_members
68+
WHERE member = %s::regrole
69+
LOOP
70+
EXECUTE format('REVOKE %%I FROM %s', r.granted_role);
71+
END LOOP;
72+
END
73+
$$;`, pq.QuoteLiteral(user.Username), pq.QuoteIdentifier(user.Username))
74+
_, err = tx.Exec(dropRolesSQL)
75+
if err != nil {
76+
return fmt.Errorf("Failed to drop roles for user %q: %w", user.Username, err)
77+
}
78+
79+
// There could be privileges on a variety of different objects.
80+
// See https://www.postgresql.org/docs/current/sql-revoke.html
81+
// But we will just worry about roles.
82+
83+
// Add roles
84+
roles := user.GrantRoles
85+
if authType == UserAuthenticationTypeIAM {
86+
roles = append(roles, "rds_iam")
87+
}
88+
for _, role := range roles {
89+
grantSQL := fmt.Sprintf("GRANT %s TO %s", pq.QuoteIdentifier(role), pq.QuoteIdentifier(user.Username))
90+
_, err = tx.Exec(grantSQL)
91+
if err != nil {
92+
return fmt.Errorf("Failed to give role %q to user %q: %w", role, user.Username, err)
93+
}
94+
}
95+
96+
// Set or remove password
97+
switch authType {
98+
case UserAuthenticationTypePassword:
99+
_, err = tx.Exec(
100+
fmt.Sprintf("ALTER USER %s WITH PASSWORD %s",
101+
pq.QuoteIdentifier(user.Username),
102+
pq.QuoteLiteral(user.Username)),
103+
)
104+
if err != nil {
105+
return fmt.Errorf("Failed to set password for user %q: %w", user.Username, err)
106+
}
107+
case UserAuthenticationTypeIAM:
108+
_, err = tx.Exec(
109+
fmt.Sprintf("ALTER USER %s WITH PASSWORD NULL",
110+
pq.QuoteIdentifier(user.Username)),
111+
)
112+
if err != nil {
113+
return fmt.Errorf("Failed to remove password for user %q: %w", user.Username, err)
114+
}
115+
default:
116+
return fmt.Errorf("Invalid authType %q", authType)
117+
}
118+
}
119+
120+
err = tx.Commit()
121+
if err != nil {
122+
return fmt.Errorf("Error committing transaction: %w", err)
123+
}
124+
125+
return nil
126+
}

0 commit comments

Comments
 (0)