Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,27 @@ _None yet. [Become a sponsor!](https://github.com/sponsors/hitman99)_
- Handles CRs in dynamically created namespaces
- Customizable secret values using templates

## AWS Specific Features when cloud_provider: "AWS"

- Enable IAM authentication for this user (PostgreSQL on AWS RDS only)

```yaml
kind: PostgresUser
....
spec:
aws:
enableIamAuth: false # (by Default false)
```
- AWS PG_repack extension installation / properly alter privileges for the owner user if cloud_provider: "AWS"

```yaml
kind: Postgres
...
spec:
extensions:
- pg_repack
```

---

## Supported Cloud Providers
Expand Down
2 changes: 1 addition & 1 deletion charts/ext-postgres-operator/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ description: |
type: application

version: 3.0.0
appVersion: "2.4.0"
appVersion: "2.5.0"
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (

require (
cel.dev/expr v0.24.0 // indirect
github.com/DATA-DOG/go-sqlmock v1.5.2 // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
Expand Down
3 changes: 3 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY=
cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw=
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU=
github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0=
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
Expand Down Expand Up @@ -82,6 +84,7 @@ github.com/joshdk/go-junit v1.0.0 h1:S86cUKIdwBHWwA6xCmFlf3RTLfVXYQfvanM5Uh+K6GE
github.com/joshdk/go-junit v1.0.0/go.mod h1:TiiV0PqkaNfFXjEiyjWM3XXrhVyCa1K4Zfga6W52ung=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kisielk/sqlstruct v0.0.0-20201105191214-5f3e10d3ab46/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
Expand Down
4 changes: 2 additions & 2 deletions internal/controller/postgres_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,8 @@ func (r *PostgresReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c
if err != nil {
return requeue(errors.NewInternalError(err))
}
// Alter database owner if the owner role was changed
err = r.pg.AlterDatabaseOwner(instance.Spec.Database, instance.Status.Roles.Owner)
// Alter database owner to desiredOwner if the owner role was changed
err = r.pg.AlterDatabaseOwner(instance.Spec.Database, desiredOwner)
if err != nil {
return requeue(errors.NewInternalError(err))
}
Expand Down
28 changes: 26 additions & 2 deletions internal/controller/postgres_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,6 @@ var _ = Describe("PostgresReconciler", func() {
// Gomock
mockCtrl = gomock.NewController(GinkgoT())
pg = mockpg.NewMockPG(mockCtrl)
pg.EXPECT().AlterDatabaseOwner(gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
pg.EXPECT().ReassignDatabaseOwner(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil).AnyTimes()
cl = k8sClient
// Create runtime scheme
sc = scheme.Scheme
Expand Down Expand Up @@ -365,6 +363,32 @@ var _ = Describe("PostgresReconciler", func() {
})
})

Context("MasterRole is changed for existing database", func() {
BeforeEach(func() {
modPostgres := postgresCR.DeepCopy()
modPostgres.Spec.MasterRole = "new-master-role"
modPostgres.Status = v1alpha1.PostgresStatus{
Succeeded: true,
Roles: v1alpha1.PostgresRoles{
Owner: "old-master-role",
},
}
initClient(modPostgres, false)
})

It("should alter database owner to the desired role", func() {
pg.EXPECT().RenameGroupRole("old-master-role", "new-master-role").Return(nil).Times(1)
pg.EXPECT().AlterDatabaseOwner(name, "new-master-role").Return(nil).Times(1)

err := runReconcile(rp, ctx, req)
Expect(err).NotTo(HaveOccurred())

foundPostgres := &v1alpha1.Postgres{}
Expect(cl.Get(ctx, types.NamespacedName{Name: name, Namespace: namespace}, foundPostgres)).To(BeNil())
Expect(foundPostgres.Status.Roles.Owner).To(Equal("new-master-role"))
})
})

Context("Correct annotation filter is set", func() {
BeforeEach(func() {
// Create client
Expand Down
52 changes: 52 additions & 0 deletions pkg/postgres/aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package postgres

import (
"fmt"
"strings"

"github.com/lib/pq"
)
Expand All @@ -10,6 +11,14 @@ type awspg struct {
pg
}

const (
AWS_ALTER_REPACK_DEFAULT_PRIVS_TABLES = `ALTER DEFAULT PRIVILEGES FOR ROLE "%s" IN SCHEMA "repack" GRANT INSERT ON TABLES TO PUBLIC`
AWS_ALTER_REPACK_DEFAULT_PRIVS_SEQUENCES = `ALTER DEFAULT PRIVILEGES FOR ROLE "%s" IN SCHEMA "repack" GRANT USAGE, SELECT ON SEQUENCES TO PUBLIC`
)

// defaults to GetConnection in production, but can be overridden in unit tests.
var awsGetConnection = GetConnection

func newAWSPG(postgres *pg) PG {
return &awspg{
*postgres,
Expand Down Expand Up @@ -38,6 +47,49 @@ func (c *awspg) CreateDB(dbname, role string) error {
return c.pg.CreateDB(dbname, role)
}

func (c *awspg) CreateExtension(dbname, extension string) error {
// Keep standard extension creation behavior for AWS as well.
err := c.pg.CreateExtension(dbname, extension)
if err != nil {
return err
}

// AWS-specific workaround is only required for pg_repack.
if !strings.EqualFold(extension, "pg_repack") {
return nil
}

return c.applyPgRepackPrivileges(dbname)
}

func (c *awspg) applyPgRepackPrivileges(dbname string) error {
var owner string
// Resolve current database owner role to target ALTER DEFAULT PRIVILEGES FOR ROLE.
err := c.db.QueryRow(fmt.Sprintf(GET_DB_OWNER, dbname)).Scan(&owner)
if err != nil {
return err
}

// Execute pg_repack privilege statements in the target database.
tmpDb, err := awsGetConnection(c.user, c.pass, c.host, dbname, c.args)
if err != nil {
return err
}
defer tmpDb.Close()

_, err = tmpDb.Exec(fmt.Sprintf(AWS_ALTER_REPACK_DEFAULT_PRIVS_TABLES, owner))
if err != nil {
return err
}

_, err = tmpDb.Exec(fmt.Sprintf(AWS_ALTER_REPACK_DEFAULT_PRIVS_SEQUENCES, owner))
if err != nil {
return err
}

return nil
}

func (c *awspg) CreateUserRole(role, password string) (string, error) {
returnedRole, err := c.pg.CreateUserRole(role, password)
if err != nil {
Expand Down
68 changes: 68 additions & 0 deletions pkg/postgres/aws_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package postgres

import (
"database/sql"
"fmt"
"regexp"
"testing"

sqlmock "github.com/DATA-DOG/go-sqlmock"
)

func TestApplyPgRepackPrivileges(t *testing.T) {
originalGetConnection := awsGetConnection
defer func() {
awsGetConnection = originalGetConnection
}()

mainDB, mainMock, err := sqlmock.New()
if err != nil {
t.Fatalf("failed to create main sqlmock: %v", err)
}
defer mainDB.Close()

tmpDB, tmpMock, err := sqlmock.New()
if err != nil {
t.Fatalf("failed to create tmp sqlmock: %v", err)
}
defer tmpDB.Close()

dbname := "test-db-dev"
owner := "test-db-dev-owner"

mainMock.ExpectQuery(regexp.QuoteMeta(fmt.Sprintf(GET_DB_OWNER, dbname))).
WillReturnRows(sqlmock.NewRows([]string{"pg_get_userbyid"}).AddRow(owner))

awsGetConnection = func(user, password, host, database, uriArgs string) (*sql.DB, error) {
if database != dbname {
t.Fatalf("expected database %s, got %s", dbname, database)
}
return tmpDB, nil
}

tmpMock.ExpectExec(regexp.QuoteMeta(fmt.Sprintf(AWS_ALTER_REPACK_DEFAULT_PRIVS_TABLES, owner))).
WillReturnResult(sqlmock.NewResult(0, 0))
tmpMock.ExpectExec(regexp.QuoteMeta(fmt.Sprintf(AWS_ALTER_REPACK_DEFAULT_PRIVS_SEQUENCES, owner))).
WillReturnResult(sqlmock.NewResult(0, 0))

c := &awspg{
pg: pg{
db: mainDB,
host: "localhost:5432",
user: "postgres",
pass: "postgres",
args: "sslmode=disable",
},
}

if err := c.applyPgRepackPrivileges(dbname); err != nil {
t.Fatalf("expected nil error, got %v", err)
}

if err := mainMock.ExpectationsWereMet(); err != nil {
t.Fatalf("main DB expectations were not met: %v", err)
}
if err := tmpMock.ExpectationsWereMet(); err != nil {
t.Fatalf("tmp DB expectations were not met: %v", err)
}
}