Skip to content

Commit 57e312c

Browse files
authored
feat: add support for custom grants (#39)
1 parent a26ab0c commit 57e312c

File tree

10 files changed

+1353
-1200
lines changed

10 files changed

+1353
-1200
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ lint: golangci-lint ## Run golangci-lint against code
6565

6666
.PHONY: test
6767
test: manifests generate fmt vet tidy envtest ## Run tests.
68-
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./... -v -coverprofile coverage.out -race
68+
KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) -p path)" go test ./... -v -coverprofile coverage.out -race
6969

7070
##@ Build
7171

api/v1beta1/postgresqluser_type.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,30 @@ import (
2020
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2121
)
2222

23+
type Privilege string
24+
25+
var SelectPrivilege Privilege = "SELECT"
26+
var AlPrivilege Privilege = "ALL"
27+
2328
type PostgreSQLUserSpec struct {
2429
// +required
2530
Database *DatabaseReference `json:"database"`
2631

2732
// +required
2833
Credentials *SecretReference `json:"credentials"`
34+
35+
// +kubebuilder:default:={{privileges: {ALL}, object: SCHEMA, objectName: public}}
36+
Grants []Grant `json:"grants,omitempty"`
37+
38+
// Roles are postgres roles granted to this user
39+
Roles []string `json:"roles,omitempty"`
40+
}
41+
42+
type Grant struct {
43+
Object string `json:"object,omitempty"`
44+
ObjectName string `json:"objectName,omitempty"`
45+
User string `json:"user,omitempty"`
46+
Privileges []Privilege `json:"privileges,omitempty"`
2947
}
3048

3149
// GetStatusConditions returns a pointer to the Status.Conditions slice

api/v1beta1/zz_generated.deepcopy.go

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

common/database/postgresql.go

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -94,21 +94,47 @@ func (s *PostgreSQLRepository) CreateDatabaseIfNotExists(ctx context.Context, da
9494
}
9595
}
9696

97-
func (s *PostgreSQLRepository) SetupUser(ctx context.Context, database string, user string, password string) error {
97+
type PostgresqlUser struct {
98+
Database string
99+
Username string
100+
Password string
101+
Roles []string
102+
Grants []Grant
103+
}
104+
105+
type Grant struct {
106+
Object string
107+
ObjectName string
108+
User string
109+
Privileges []Privilege
110+
}
111+
112+
type Privilege string
113+
114+
var SelectPrivilege Privilege = "SELECT"
115+
var AlPrivilege Privilege = "ALL"
116+
117+
func (s *PostgreSQLRepository) SetupUser(ctx context.Context, user PostgresqlUser) error {
98118
if err := s.createUserIfNotExists(ctx, user); err != nil {
99-
return err
119+
return fmt.Errorf("failed to create user: %w", err)
100120
}
101-
if err := s.setPasswordForUser(ctx, user, password); err != nil {
102-
return err
121+
if err := s.setPasswordForUser(ctx, user); err != nil {
122+
return fmt.Errorf("failed to set password: %w", err)
103123
}
104-
if err := s.grantAllPrivileges(ctx, database, user); err != nil {
105-
return err
124+
if err := s.grantAllPrivileges(ctx, user); err != nil {
125+
return fmt.Errorf("failed to grant all privileges: %w", err)
126+
}
127+
if err := s.grantRoles(ctx, user); err != nil {
128+
return fmt.Errorf("failed to grant roles: %w", err)
129+
}
130+
if err := s.grantRules(ctx, user); err != nil {
131+
return fmt.Errorf("failed to apply grant rules: %w", err)
106132
}
107133
return nil
108134
}
109135

110-
func (s *PostgreSQLRepository) DropUser(ctx context.Context, database string, user string) error {
111-
if err := s.RevokeAllPrivileges(ctx, database, user); err != nil {
136+
func (s *PostgreSQLRepository) DropUser(ctx context.Context, user PostgresqlUser) error {
137+
if err := s.RevokeAllPrivileges(ctx, user); err != nil {
112138
return err
113139
}
114140
if err := s.dropUserIfNotExist(ctx, user); err != nil {
@@ -126,14 +152,14 @@ func (s *PostgreSQLRepository) EnableExtension(ctx context.Context, db, name str
126152
return nil
127153
}
128154

129-
func (s *PostgreSQLRepository) createUserIfNotExists(ctx context.Context, user string) error {
155+
func (s *PostgreSQLRepository) createUserIfNotExists(ctx context.Context, user PostgresqlUser) error {
130156
if userExists, err := s.doesUserExist(ctx, user); err != nil {
131157
return err
132158
} else {
133159
if userExists {
134160
return nil
135161
}
136-
if _, err := s.conn.Exec(ctx, fmt.Sprintf("CREATE USER %s;", (pgx.Identifier{user}).Sanitize())); err != nil {
162+
if _, err := s.conn.Exec(ctx, fmt.Sprintf("CREATE USER %s;", (pgx.Identifier{user.Username}).Sanitize())); err != nil {
137163
return err
138164
} else {
139165
if userExistsNow, err := s.doesUserExist(ctx, user); err != nil {
@@ -154,14 +180,14 @@ func (s *PostgreSQLRepository) createExtension(ctx context.Context, db, name str
154180
return err
155181
}
156182

157-
func (s *PostgreSQLRepository) dropUserIfNotExist(ctx context.Context, user string) error {
183+
func (s *PostgreSQLRepository) dropUserIfNotExist(ctx context.Context, user PostgresqlUser) error {
158184
if userExists, err := s.doesUserExist(ctx, user); err != nil {
159185
return err
160186
} else {
161187
if !userExists {
162188
return nil
163189
}
164-
if _, err := s.conn.Exec(ctx, fmt.Sprintf("DROP USER %s;", (pgx.Identifier{user}).Sanitize())); err != nil {
190+
if _, err := s.conn.Exec(ctx, fmt.Sprintf("DROP USER %s;", (pgx.Identifier{user.Username}).Sanitize())); err != nil {
165191
return err
166192
} else {
167193
if userExistsNow, err := s.doesUserExist(ctx, user); err != nil {
@@ -177,23 +203,48 @@ func (s *PostgreSQLRepository) dropUserIfNotExist(ctx context.Context, user stri
177203
}
178204
}
179205

180-
func (s *PostgreSQLRepository) setPasswordForUser(ctx context.Context, user string, password string) error {
181-
password, err := s.conn.PgConn().EscapeString(password)
206+
func (s *PostgreSQLRepository) setPasswordForUser(ctx context.Context, user PostgresqlUser) error {
207+
password, err := s.conn.PgConn().EscapeString(user.Password)
182208
if err != nil {
183209
return err
184210
}
185211

186-
_, err = s.conn.Exec(ctx, fmt.Sprintf("ALTER USER %s WITH ENCRYPTED PASSWORD '%s';", (pgx.Identifier{user}).Sanitize(), password))
212+
_, err = s.conn.Exec(ctx, fmt.Sprintf("ALTER USER %s WITH ENCRYPTED PASSWORD '%s';", (pgx.Identifier{user.Username}).Sanitize(), password))
187213
return err
188214
}
189215

190-
func (s *PostgreSQLRepository) grantAllPrivileges(ctx context.Context, database string, user string) error {
191-
_, err := s.conn.Exec(ctx, fmt.Sprintf("GRANT ALL PRIVILEGES ON DATABASE %s TO %s;", (pgx.Identifier{database}).Sanitize(), (pgx.Identifier{user}).Sanitize()))
216+
func (s *PostgreSQLRepository) grantAllPrivileges(ctx context.Context, user PostgresqlUser) error {
217+
_, err := s.conn.Exec(ctx, fmt.Sprintf("GRANT ALL PRIVILEGES ON DATABASE %s TO %s;", (pgx.Identifier{user.Database}).Sanitize(), (pgx.Identifier{user.Username}).Sanitize()))
192218
return err
193219
}
194220

195-
func (s *PostgreSQLRepository) RevokeAllPrivileges(ctx context.Context, database string, user string) error {
196-
_, err := s.conn.Exec(ctx, fmt.Sprintf("REVOKE ALL PRIVILEGES ON DATABASE %s FROM %s;", (pgx.Identifier{database}).Sanitize(), (pgx.Identifier{user}).Sanitize()))
221+
func (s *PostgreSQLRepository) grantRoles(ctx context.Context, user PostgresqlUser) error {
222+
for _, role := range user.Roles {
223+
_, err := s.conn.Exec(ctx, fmt.Sprintf("GRANT %s TO %s;", (pgx.Identifier{role}).Sanitize(), (pgx.Identifier{user.Username}).Sanitize()))
224+
if err != nil {
225+
return err
226+
}
227+
}
228+
229+
return nil
230+
}
231+
232+
func (s *PostgreSQLRepository) grantRules(ctx context.Context, user PostgresqlUser) error {
233+
for _, grant := range user.Grants {
234+
for _, p := range grant.Privileges {
235+
_, err := s.conn.Exec(ctx, fmt.Sprintf("GRANT %s ON %s %s TO %s;", string(p), grant.Object, (pgx.Identifier{grant.ObjectName}).Sanitize(), (pgx.Identifier{user.Username}).Sanitize()))
236+
if err != nil {
237+
return err
238+
}
239+
}
240+
241+
}
242+
243+
return nil
244+
}
245+
246+
func (s *PostgreSQLRepository) RevokeAllPrivileges(ctx context.Context, user PostgresqlUser) error {
247+
_, err := s.conn.Exec(ctx, fmt.Sprintf("REVOKE ALL PRIVILEGES ON DATABASE %s FROM %s;", (pgx.Identifier{user.Database}).Sanitize(), (pgx.Identifier{user.Username}).Sanitize()))
197248
return err
198249
}
199250

@@ -214,14 +265,14 @@ func (s *PostgreSQLRepository) doesDatabaseExist(ctx context.Context, database s
214265
return result == 1, nil
215266
}
216267

217-
func (s *PostgreSQLRepository) doesUserExist(ctx context.Context, user string) (bool, error) {
218-
user, err := s.conn.PgConn().EscapeString(user)
268+
func (s *PostgreSQLRepository) doesUserExist(ctx context.Context, user PostgresqlUser) (bool, error) {
269+
username, err := s.conn.PgConn().EscapeString(user.Username)
219270
if err != nil {
220271
return false, err
221272
}
222273

223274
var result int64
224-
err = s.conn.QueryRow(ctx, fmt.Sprintf("SELECT 1 FROM pg_roles WHERE rolname='%s';", user)).Scan(&result)
275+
err = s.conn.QueryRow(ctx, fmt.Sprintf("SELECT 1 FROM pg_roles WHERE rolname='%s';", username)).Scan(&result)
225276
if err != nil {
226277
if err == pgx.ErrNoRows {
227278
return false, nil

config/base/crd/bases/dbprovisioning.infra.doodle.com_postgresqlusers.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,31 @@ spec:
7777
required:
7878
- name
7979
type: object
80+
grants:
81+
default:
82+
- object: SCHEMA
83+
objectName: public
84+
privileges:
85+
- ALL
86+
items:
87+
properties:
88+
object:
89+
type: string
90+
objectName:
91+
type: string
92+
privileges:
93+
items:
94+
type: string
95+
type: array
96+
user:
97+
type: string
98+
type: object
99+
type: array
100+
roles:
101+
description: Roles are postgres roles granted to this user
102+
items:
103+
type: string
104+
type: array
80105
required:
81106
- credentials
82107
- database

0 commit comments

Comments
 (0)