Skip to content
Merged
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
2 changes: 0 additions & 2 deletions .github/workflows/govulncheck.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ jobs:
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: 'govulncheck-results.sarif'
# TODO: https://go.dev/issue/70157
if: ${{ false }}

# Print any detected vulnerabilities to the workflow log. This step fails
# when the tool detects a vulnerability in code that is called.
Expand Down
49 changes: 43 additions & 6 deletions internal/controller/postgrescluster/pgbackrest.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,9 +122,9 @@ type RepoResources struct {
// strategy.
func (r *Reconciler) applyRepoHostIntent(ctx context.Context, postgresCluster *v1beta1.PostgresCluster,
repoHostName string, repoResources *RepoResources,
observedInstances *observedInstances) (*appsv1.StatefulSet, error) {
observedInstances *observedInstances, saName string) (*appsv1.StatefulSet, error) {

repo, err := r.generateRepoHostIntent(ctx, postgresCluster, repoHostName, repoResources, observedInstances)
repo, err := r.generateRepoHostIntent(ctx, postgresCluster, repoHostName, repoResources, observedInstances, saName)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -567,7 +567,7 @@ func (r *Reconciler) setScheduledJobStatus(ctx context.Context,
// as needed to create and reconcile a pgBackRest dedicated repository host within the kubernetes
// cluster.
func (r *Reconciler) generateRepoHostIntent(ctx context.Context, postgresCluster *v1beta1.PostgresCluster,
repoHostName string, repoResources *RepoResources, observedInstances *observedInstances,
repoHostName string, repoResources *RepoResources, observedInstances *observedInstances, saName string,
) (*appsv1.StatefulSet, error) {

annotations := naming.Merge(
Expand Down Expand Up @@ -681,6 +681,8 @@ func (r *Reconciler) generateRepoHostIntent(ctx context.Context, postgresCluster

repo.Spec.Template.Spec.SecurityContext = postgres.PodSecurityContext(postgresCluster)

repo.Spec.Template.Spec.ServiceAccountName = saName

pgbackrest.AddServerToRepoPod(ctx, postgresCluster, &repo.Spec.Template.Spec)

if pgbackrest.RepoHostVolumeDefined(postgresCluster) {
Expand Down Expand Up @@ -1380,10 +1382,18 @@ func (r *Reconciler) reconcilePGBackRest(ctx context.Context,
return result, nil
}

// reconcile the RBAC required to run the pgBackRest Repo Host
repoHostSA, err := r.reconcileRepoHostRBAC(ctx, postgresCluster)
if err != nil {
log.Error(err, "unable to reconcile pgBackRest repo host RBAC")
result.Requeue = true
return result, nil
}

var repoHost *appsv1.StatefulSet
var repoHostName string
// reconcile the pgbackrest repository host
repoHost, err = r.reconcileDedicatedRepoHost(ctx, postgresCluster, repoResources, instances)
repoHost, err = r.reconcileDedicatedRepoHost(ctx, postgresCluster, repoResources, instances, repoHostSA.GetName())
if err != nil {
log.Error(err, "unable to reconcile pgBackRest repo host")
result.Requeue = true
Expand Down Expand Up @@ -2118,12 +2128,39 @@ func (r *Reconciler) reconcilePGBackRestRBAC(ctx context.Context,
return sa, nil
}

// +kubebuilder:rbac:groups="",resources="serviceaccounts",verbs={create,patch}

// reconcileRepoHostRBAC reconciles the ServiceAccount for the pgBackRest repo host
func (r *Reconciler) reconcileRepoHostRBAC(ctx context.Context,
postgresCluster *v1beta1.PostgresCluster) (*corev1.ServiceAccount, error) {

sa := &corev1.ServiceAccount{ObjectMeta: naming.RepoHostRBAC(postgresCluster)}
sa.SetGroupVersionKind(corev1.SchemeGroupVersion.WithKind("ServiceAccount"))

if err := r.setControllerReference(postgresCluster, sa); err != nil {
return nil, errors.WithStack(err)
}

sa.Annotations = naming.Merge(postgresCluster.Spec.Metadata.GetAnnotationsOrNil(),
postgresCluster.Spec.Backups.PGBackRest.Metadata.GetAnnotationsOrNil())
sa.Labels = naming.Merge(postgresCluster.Spec.Metadata.GetLabelsOrNil(),
postgresCluster.Spec.Backups.PGBackRest.Metadata.GetLabelsOrNil(),
naming.PGBackRestLabels(postgresCluster.GetName()))

if err := r.apply(ctx, sa); err != nil {
return nil, errors.WithStack(err)
}

return sa, nil
}

// reconcileDedicatedRepoHost is responsible for reconciling a pgBackRest dedicated repository host
// StatefulSet according to a specific PostgresCluster custom resource.
func (r *Reconciler) reconcileDedicatedRepoHost(ctx context.Context,
postgresCluster *v1beta1.PostgresCluster,
repoResources *RepoResources,
observedInstances *observedInstances) (*appsv1.StatefulSet, error) {
observedInstances *observedInstances,
saName string) (*appsv1.StatefulSet, error) {

log := logging.FromContext(ctx).WithValues("reconcileResource", "repoHost")

Expand Down Expand Up @@ -2164,7 +2201,7 @@ func (r *Reconciler) reconcileDedicatedRepoHost(ctx context.Context,
}
repoHostName := repoResources.hosts[0].Name
repoHost, err := r.applyRepoHostIntent(ctx, postgresCluster, repoHostName, repoResources,
observedInstances)
observedInstances, saName)
if err != nil {
log.Error(err, "reconciling repository host")
return nil, err
Expand Down
46 changes: 42 additions & 4 deletions internal/controller/postgrescluster/pgbackrest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,8 @@ schedulerName: default-scheduler
securityContext:
fsGroup: 26
fsGroupChangePolicy: OnRootMismatch
serviceAccount: hippocluster-repohost
serviceAccountName: hippocluster-repohost
shareProcessNamespace: true
terminationGracePeriodSeconds: 30
tolerations:
Expand Down Expand Up @@ -724,6 +726,42 @@ func TestReconcilePGBackRestRBAC(t *testing.T) {
assert.Assert(t, foundSubject)
}

func TestReconcileRepoHostRBAC(t *testing.T) {
// Garbage collector cleans up test resources before the test completes
if strings.EqualFold(os.Getenv("USE_EXISTING_CLUSTER"), "true") {
t.Skip("USE_EXISTING_CLUSTER: Test fails due to garbage collection")
}

ctx := context.Background()
_, tClient := setupKubernetes(t)
require.ParallelCapacity(t, 0)

r := &Reconciler{Client: tClient, Owner: client.FieldOwner(t.Name())}

clusterName := "hippocluster"
clusterUID := "hippouid"

ns := setupNamespace(t, tClient)

// create a PostgresCluster to test with
postgresCluster := fakePostgresCluster(clusterName, ns.GetName(), clusterUID, true)
postgresCluster.Status.PGBackRest = &v1beta1.PGBackRestStatus{
Repos: []v1beta1.RepoStatus{{Name: "repo1", StanzaCreated: false}},
}

serviceAccount, err := r.reconcileRepoHostRBAC(ctx, postgresCluster)
assert.NilError(t, err)
assert.Assert(t, serviceAccount != nil)

// verify the service account has been created
sa := &corev1.ServiceAccount{}
err = tClient.Get(ctx, types.NamespacedName{
Name: naming.RepoHostRBAC(postgresCluster).Name,
Namespace: postgresCluster.GetNamespace(),
}, sa)
assert.NilError(t, err)
}

func TestReconcileStanzaCreate(t *testing.T) {
cfg, tClient := setupKubernetes(t)
require.ParallelCapacity(t, 0)
Expand Down Expand Up @@ -2672,12 +2710,12 @@ func TestGenerateRepoHostIntent(t *testing.T) {

t.Run("empty", func(t *testing.T) {
_, err := r.generateRepoHostIntent(ctx, &v1beta1.PostgresCluster{}, "", &RepoResources{},
&observedInstances{})
&observedInstances{}, "")
assert.NilError(t, err)
})

cluster := &v1beta1.PostgresCluster{}
sts, err := r.generateRepoHostIntent(ctx, cluster, "", &RepoResources{}, &observedInstances{})
sts, err := r.generateRepoHostIntent(ctx, cluster, "", &RepoResources{}, &observedInstances{}, "")
assert.NilError(t, err)

t.Run("ServiceAccount", func(t *testing.T) {
Expand All @@ -2698,7 +2736,7 @@ func TestGenerateRepoHostIntent(t *testing.T) {
},
}
observed := &observedInstances{forCluster: []*Instance{{Pods: []*corev1.Pod{{}}}}}
sts, err := r.generateRepoHostIntent(ctx, cluster, "", &RepoResources{}, observed)
sts, err := r.generateRepoHostIntent(ctx, cluster, "", &RepoResources{}, observed, "")
assert.NilError(t, err)
assert.Equal(t, *sts.Spec.Replicas, int32(1))
})
Expand All @@ -2710,7 +2748,7 @@ func TestGenerateRepoHostIntent(t *testing.T) {
},
}
observed := &observedInstances{forCluster: []*Instance{{}}}
sts, err := r.generateRepoHostIntent(ctx, cluster, "", &RepoResources{}, observed)
sts, err := r.generateRepoHostIntent(ctx, cluster, "", &RepoResources{}, observed, "")
assert.NilError(t, err)
assert.Equal(t, *sts.Spec.Replicas, int32(0))
})
Expand Down
2 changes: 1 addition & 1 deletion internal/controller/postgrescluster/pgmonitor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -602,7 +602,7 @@ func TestReconcilePGMonitorExporterStatus(t *testing.T) {
podExecCalled: false,
// Status was generated manually for this test case
// TODO (jmckulk): add code to generate status
status: v1beta1.MonitoringStatus{ExporterConfiguration: "6d874c58df"},
status: v1beta1.MonitoringStatus{ExporterConfiguration: "5c5f955485"},
statusChangedAfterReconcile: false,
}} {
t.Run(test.name, func(t *testing.T) {
Expand Down
9 changes: 9 additions & 0 deletions internal/naming/names.go
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,15 @@ func PGBackRestRBAC(cluster *v1beta1.PostgresCluster) metav1.ObjectMeta {
}
}

// RepoHostRBAC returns the ObjectMeta necessary to lookup the ServiceAccount for
// the pgBackRest Repo Host
func RepoHostRBAC(cluster *v1beta1.PostgresCluster) metav1.ObjectMeta {
return metav1.ObjectMeta{
Namespace: cluster.Namespace,
Name: cluster.Name + "-repohost",
}
}

// PGBackRestRepoVolume returns the ObjectMeta for a pgBackRest repository volume
func PGBackRestRepoVolume(cluster *v1beta1.PostgresCluster,
repoName string) metav1.ObjectMeta {
Expand Down
2 changes: 2 additions & 0 deletions internal/pgaudit/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ func EnableInPostgreSQL(ctx context.Context, exec postgres.Executor) error {

stdout, stderr, err := exec.ExecInAllDatabases(ctx,
// Quiet the NOTICE from IF EXISTS, and install the pgAudit event triggers.
// Use the default setting for "synchronous_commit".
// - https://www.postgresql.org/docs/current/runtime-config-client.html
// - https://www.postgresql.org/docs/current/runtime-config-wal.html
// - https://github.com/pgaudit/pgaudit#settings
`SET client_min_messages = WARNING; CREATE EXTENSION IF NOT EXISTS pgaudit;`,
map[string]string{
Expand Down
10 changes: 9 additions & 1 deletion internal/pgbouncer/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ func DisableInPostgreSQL(ctx context.Context, exec postgres.Executor) error {
// - https://www.postgresql.org/docs/current/runtime-config-client.html
`SET client_min_messages = WARNING;`,

// Do not wait for changes to be replicated. [Since PostgreSQL v9.1]
// - https://www.postgresql.org/docs/current/runtime-config-wal.html
`SET synchronous_commit = LOCAL;`,

// Drop the following objects in a transaction.
`BEGIN;`,

Expand Down Expand Up @@ -102,7 +106,7 @@ SELECT pg_catalog.format('DROP OWNED BY %I CASCADE', :'username')
// Remove the PgBouncer user now that the objects and other privileges are gone.
stdout, stderr, err = exec.ExecInDatabasesFromQuery(ctx,
`SELECT pg_catalog.current_database()`,
`SET client_min_messages = WARNING; DROP ROLE IF EXISTS :"username";`,
`SET client_min_messages = WARNING; SET synchronous_commit = LOCAL; DROP ROLE IF EXISTS :"username";`,
map[string]string{
"username": postgresqlUser,

Expand Down Expand Up @@ -130,6 +134,10 @@ func EnableInPostgreSQL(
// - https://www.postgresql.org/docs/current/runtime-config-client.html
`SET client_min_messages = WARNING;`,

// Do not wait for changes to be replicated. [Since PostgreSQL v9.1]
// - https://www.postgresql.org/docs/current/runtime-config-wal.html
`SET synchronous_commit = LOCAL;`,

// Create the following objects in a transaction so that permissions
// are correct before any other session sees them.
// - https://www.postgresql.org/docs/current/ddl-priv.html
Expand Down
4 changes: 3 additions & 1 deletion internal/pgbouncer/postgres_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func TestDisableInPostgreSQL(t *testing.T) {
assert.NilError(t, err)
assert.Equal(t, string(b), strings.TrimSpace(`
SET client_min_messages = WARNING;
SET synchronous_commit = LOCAL;
BEGIN;
DROP FUNCTION IF EXISTS :"namespace".get_auth(username TEXT);
DROP SCHEMA IF EXISTS :"namespace" CASCADE;
Expand Down Expand Up @@ -90,7 +91,7 @@ COMMIT;`))

b, err := io.ReadAll(stdin)
assert.NilError(t, err)
assert.Equal(t, string(b), `SET client_min_messages = WARNING; DROP ROLE IF EXISTS :"username";`)
assert.Equal(t, string(b), `SET client_min_messages = WARNING; SET synchronous_commit = LOCAL; DROP ROLE IF EXISTS :"username";`)
gomega.NewWithT(t).Expect(command).To(gomega.ContainElements(
`--set=username=_crunchypgbouncer`,
), "expected query parameters")
Expand Down Expand Up @@ -135,6 +136,7 @@ func TestEnableInPostgreSQL(t *testing.T) {
assert.NilError(t, err)
assert.Equal(t, string(b), strings.TrimSpace(`
SET client_min_messages = WARNING;
SET synchronous_commit = LOCAL;
BEGIN;
SELECT pg_catalog.format('CREATE ROLE %I NOLOGIN', :'username')
WHERE NOT EXISTS (SELECT 1 FROM pg_catalog.pg_roles WHERE rolname = :'username')
Expand Down
8 changes: 8 additions & 0 deletions internal/pgmonitor/postgres.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,10 @@ func EnableExporterInPostgreSQL(ctx context.Context, exec postgres.Executor,
// - https://www.postgresql.org/docs/current/runtime-config-client.html
`SET client_min_messages = WARNING;`,

// Do not wait for changes to be replicated. [Since PostgreSQL v9.1]
// - https://www.postgresql.org/docs/current/runtime-config-wal.html
`SET synchronous_commit = LOCAL;`,

// Exporter expects that extension(s) to be installed in all databases
// pg_stat_statements: https://access.crunchydata.com/documentation/pgmonitor/latest/exporter/
"CREATE EXTENSION IF NOT EXISTS pg_stat_statements;",
Expand All @@ -103,6 +107,10 @@ func EnableExporterInPostgreSQL(ctx context.Context, exec postgres.Executor,
// - https://www.postgresql.org/docs/current/runtime-config-client.html
`SET client_min_messages = WARNING;`,

// Do not wait for changes to be replicated. [Since PostgreSQL v9.1]
// - https://www.postgresql.org/docs/current/runtime-config-wal.html
`SET synchronous_commit = LOCAL;`,

// Setup.sql file from the exporter image. sql is specific
// to the PostgreSQL version
setup,
Expand Down
4 changes: 4 additions & 0 deletions internal/postgis/postgis.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ func EnableInPostgreSQL(ctx context.Context, exec postgres.Executor) error {
// - https://www.postgresql.org/docs/current/runtime-config-client.html
`SET client_min_messages = WARNING;`,

// Do not wait for changes to be replicated. [Since PostgreSQL v9.1]
// - https://www.postgresql.org/docs/current/runtime-config-wal.html
`SET synchronous_commit = LOCAL;`,

`CREATE EXTENSION IF NOT EXISTS postgis;`,
`CREATE EXTENSION IF NOT EXISTS postgis_topology;`,
`CREATE EXTENSION IF NOT EXISTS fuzzystrmatch;`,
Expand Down
1 change: 1 addition & 0 deletions internal/postgis/postgis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func TestEnableInPostgreSQL(t *testing.T) {
b, err := io.ReadAll(stdin)
assert.NilError(t, err)
assert.Equal(t, string(b), `SET client_min_messages = WARNING;
SET synchronous_commit = LOCAL;
CREATE EXTENSION IF NOT EXISTS postgis;
CREATE EXTENSION IF NOT EXISTS postgis_topology;
CREATE EXTENSION IF NOT EXISTS fuzzystrmatch;
Expand Down
8 changes: 8 additions & 0 deletions internal/postgres/users.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,10 @@ func WriteUsersInPostgreSQL(
var err error
var sql bytes.Buffer

// Do not wait for changes to be replicated. [Since PostgreSQL v9.1]
// - https://www.postgresql.org/docs/current/runtime-config-wal.html
_, _ = sql.WriteString(`SET synchronous_commit = LOCAL;`)

// Prevent unexpected dereferences by emptying "search_path". The "pg_catalog"
// schema is still searched, and only temporary objects can be created.
// - https://www.postgresql.org/docs/current/runtime-config-client.html#GUC-SEARCH-PATH
Expand Down Expand Up @@ -219,6 +223,10 @@ func WriteUsersSchemasInPostgreSQL(ctx context.Context, exec Executor,
// - https://www.postgresql.org/docs/current/runtime-config-client.html
`SET client_min_messages = WARNING;`,

// Do not wait for changes to be replicated. [Since PostgreSQL v9.1]
// - https://www.postgresql.org/docs/current/runtime-config-wal.html
`SET synchronous_commit = LOCAL;`,

// Creates a schema named after and owned by the user
// - https://www.postgresql.org/docs/current/ddl-schemas.html
// - https://www.postgresql.org/docs/current/sql-createschema.html
Expand Down
2 changes: 1 addition & 1 deletion internal/postgres/users_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ func TestWriteUsersInPostgreSQL(t *testing.T) {
b, err := io.ReadAll(stdin)
assert.NilError(t, err)
assert.Equal(t, string(b), strings.TrimSpace(`
SET search_path TO '';
SET synchronous_commit = LOCAL;SET search_path TO '';
CREATE TEMPORARY TABLE input (id serial, data json);
\copy input (data) from stdin with (format text)
\.
Expand Down