Skip to content

Commit

Permalink
feat: add support for custom grants (#39)
Browse files Browse the repository at this point in the history
  • Loading branch information
raffis committed Jun 12, 2023
1 parent a26ab0c commit 57e312c
Show file tree
Hide file tree
Showing 10 changed files with 1,353 additions and 1,200 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ lint: golangci-lint ## Run golangci-lint against code

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

##@ Build

Expand Down
18 changes: 18 additions & 0 deletions api/v1beta1/postgresqluser_type.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,30 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

type Privilege string

var SelectPrivilege Privilege = "SELECT"
var AlPrivilege Privilege = "ALL"

type PostgreSQLUserSpec struct {
// +required
Database *DatabaseReference `json:"database"`

// +required
Credentials *SecretReference `json:"credentials"`

// +kubebuilder:default:={{privileges: {ALL}, object: SCHEMA, objectName: public}}
Grants []Grant `json:"grants,omitempty"`

// Roles are postgres roles granted to this user
Roles []string `json:"roles,omitempty"`
}

type Grant struct {
Object string `json:"object,omitempty"`
ObjectName string `json:"objectName,omitempty"`
User string `json:"user,omitempty"`
Privileges []Privilege `json:"privileges,omitempty"`
}

// GetStatusConditions returns a pointer to the Status.Conditions slice
Expand Down
32 changes: 32 additions & 0 deletions api/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

95 changes: 73 additions & 22 deletions common/database/postgresql.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,21 +94,47 @@ func (s *PostgreSQLRepository) CreateDatabaseIfNotExists(ctx context.Context, da
}
}

func (s *PostgreSQLRepository) SetupUser(ctx context.Context, database string, user string, password string) error {
type PostgresqlUser struct {
Database string
Username string
Password string
Roles []string
Grants []Grant
}

type Grant struct {
Object string
ObjectName string
User string
Privileges []Privilege
}

type Privilege string

var SelectPrivilege Privilege = "SELECT"
var AlPrivilege Privilege = "ALL"

func (s *PostgreSQLRepository) SetupUser(ctx context.Context, user PostgresqlUser) error {
if err := s.createUserIfNotExists(ctx, user); err != nil {
return err
return fmt.Errorf("failed to create user: %w", err)
}
if err := s.setPasswordForUser(ctx, user, password); err != nil {
return err
if err := s.setPasswordForUser(ctx, user); err != nil {
return fmt.Errorf("failed to set password: %w", err)
}
if err := s.grantAllPrivileges(ctx, database, user); err != nil {
return err
if err := s.grantAllPrivileges(ctx, user); err != nil {
return fmt.Errorf("failed to grant all privileges: %w", err)
}
if err := s.grantRoles(ctx, user); err != nil {
return fmt.Errorf("failed to grant roles: %w", err)
}
if err := s.grantRules(ctx, user); err != nil {
return fmt.Errorf("failed to apply grant rules: %w", err)
}
return nil
}

func (s *PostgreSQLRepository) DropUser(ctx context.Context, database string, user string) error {
if err := s.RevokeAllPrivileges(ctx, database, user); err != nil {
func (s *PostgreSQLRepository) DropUser(ctx context.Context, user PostgresqlUser) error {
if err := s.RevokeAllPrivileges(ctx, user); err != nil {
return err
}
if err := s.dropUserIfNotExist(ctx, user); err != nil {
Expand All @@ -126,14 +152,14 @@ func (s *PostgreSQLRepository) EnableExtension(ctx context.Context, db, name str
return nil
}

func (s *PostgreSQLRepository) createUserIfNotExists(ctx context.Context, user string) error {
func (s *PostgreSQLRepository) createUserIfNotExists(ctx context.Context, user PostgresqlUser) error {
if userExists, err := s.doesUserExist(ctx, user); err != nil {
return err
} else {
if userExists {
return nil
}
if _, err := s.conn.Exec(ctx, fmt.Sprintf("CREATE USER %s;", (pgx.Identifier{user}).Sanitize())); err != nil {
if _, err := s.conn.Exec(ctx, fmt.Sprintf("CREATE USER %s;", (pgx.Identifier{user.Username}).Sanitize())); err != nil {
return err
} else {
if userExistsNow, err := s.doesUserExist(ctx, user); err != nil {
Expand All @@ -154,14 +180,14 @@ func (s *PostgreSQLRepository) createExtension(ctx context.Context, db, name str
return err
}

func (s *PostgreSQLRepository) dropUserIfNotExist(ctx context.Context, user string) error {
func (s *PostgreSQLRepository) dropUserIfNotExist(ctx context.Context, user PostgresqlUser) error {
if userExists, err := s.doesUserExist(ctx, user); err != nil {
return err
} else {
if !userExists {
return nil
}
if _, err := s.conn.Exec(ctx, fmt.Sprintf("DROP USER %s;", (pgx.Identifier{user}).Sanitize())); err != nil {
if _, err := s.conn.Exec(ctx, fmt.Sprintf("DROP USER %s;", (pgx.Identifier{user.Username}).Sanitize())); err != nil {
return err
} else {
if userExistsNow, err := s.doesUserExist(ctx, user); err != nil {
Expand All @@ -177,23 +203,48 @@ func (s *PostgreSQLRepository) dropUserIfNotExist(ctx context.Context, user stri
}
}

func (s *PostgreSQLRepository) setPasswordForUser(ctx context.Context, user string, password string) error {
password, err := s.conn.PgConn().EscapeString(password)
func (s *PostgreSQLRepository) setPasswordForUser(ctx context.Context, user PostgresqlUser) error {
password, err := s.conn.PgConn().EscapeString(user.Password)
if err != nil {
return err
}

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

func (s *PostgreSQLRepository) grantAllPrivileges(ctx context.Context, database string, user string) error {
_, err := s.conn.Exec(ctx, fmt.Sprintf("GRANT ALL PRIVILEGES ON DATABASE %s TO %s;", (pgx.Identifier{database}).Sanitize(), (pgx.Identifier{user}).Sanitize()))
func (s *PostgreSQLRepository) grantAllPrivileges(ctx context.Context, user PostgresqlUser) error {
_, 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()))
return err
}

func (s *PostgreSQLRepository) RevokeAllPrivileges(ctx context.Context, database string, user string) error {
_, err := s.conn.Exec(ctx, fmt.Sprintf("REVOKE ALL PRIVILEGES ON DATABASE %s FROM %s;", (pgx.Identifier{database}).Sanitize(), (pgx.Identifier{user}).Sanitize()))
func (s *PostgreSQLRepository) grantRoles(ctx context.Context, user PostgresqlUser) error {
for _, role := range user.Roles {
_, err := s.conn.Exec(ctx, fmt.Sprintf("GRANT %s TO %s;", (pgx.Identifier{role}).Sanitize(), (pgx.Identifier{user.Username}).Sanitize()))
if err != nil {
return err
}
}

return nil
}

func (s *PostgreSQLRepository) grantRules(ctx context.Context, user PostgresqlUser) error {
for _, grant := range user.Grants {
for _, p := range grant.Privileges {
_, 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()))
if err != nil {
return err
}
}

}

return nil
}

func (s *PostgreSQLRepository) RevokeAllPrivileges(ctx context.Context, user PostgresqlUser) error {
_, 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()))
return err
}

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

func (s *PostgreSQLRepository) doesUserExist(ctx context.Context, user string) (bool, error) {
user, err := s.conn.PgConn().EscapeString(user)
func (s *PostgreSQLRepository) doesUserExist(ctx context.Context, user PostgresqlUser) (bool, error) {
username, err := s.conn.PgConn().EscapeString(user.Username)
if err != nil {
return false, err
}

var result int64
err = s.conn.QueryRow(ctx, fmt.Sprintf("SELECT 1 FROM pg_roles WHERE rolname='%s';", user)).Scan(&result)
err = s.conn.QueryRow(ctx, fmt.Sprintf("SELECT 1 FROM pg_roles WHERE rolname='%s';", username)).Scan(&result)
if err != nil {
if err == pgx.ErrNoRows {
return false, nil
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,31 @@ spec:
required:
- name
type: object
grants:
default:
- object: SCHEMA
objectName: public
privileges:
- ALL
items:
properties:
object:
type: string
objectName:
type: string
privileges:
items:
type: string
type: array
user:
type: string
type: object
type: array
roles:
description: Roles are postgres roles granted to this user
items:
type: string
type: array
required:
- credentials
- database
Expand Down
Loading

0 comments on commit 57e312c

Please sign in to comment.