Skip to content

Commit f7b18d4

Browse files
philrhurstPhilip Hurst
and
Philip Hurst
authored
pgbouncer user password change should not require verifier (#4084)
* regenerate verifier only when user updates pgBouncer Secret password * improve logic for calculating verifier * refactor to remove generatePassword func * added comment describing MD5/SCRAM requirements * added test for SCRAM verifier * refactored logic to clearly capture four possible events * refactored test * simplified logic * removed empty branch to pass linter * updated test to check for setting verifier only --------- Co-authored-by: Philip Hurst <[email protected]>
1 parent b4be754 commit f7b18d4

File tree

3 files changed

+67
-20
lines changed

3 files changed

+67
-20
lines changed

internal/pgbouncer/postgres.go

-17
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ import (
1212

1313
"github.com/crunchydata/postgres-operator/internal/logging"
1414
"github.com/crunchydata/postgres-operator/internal/postgres"
15-
"github.com/crunchydata/postgres-operator/internal/postgres/password"
16-
"github.com/crunchydata/postgres-operator/internal/util"
1715
)
1816

1917
const (
@@ -203,21 +201,6 @@ REVOKE ALL PRIVILEGES
203201
return err
204202
}
205203

206-
func generatePassword() (plaintext, verifier string, err error) {
207-
// PgBouncer can login to PostgreSQL using either MD5 or SCRAM-SHA-256.
208-
// When using MD5, the (hashed) verifier can be stored in PgBouncer's
209-
// authentication file. When using SCRAM, the plaintext password must be
210-
// stored.
211-
// - https://www.pgbouncer.org/config.html#authentication-file-format
212-
// - https://github.com/pgbouncer/pgbouncer/issues/508#issuecomment-713339834
213-
214-
plaintext, err = util.GenerateASCIIPassword(32)
215-
if err == nil {
216-
verifier, err = password.NewSCRAMPassword(plaintext).Build()
217-
}
218-
return
219-
}
220-
221204
func postgresqlHBAs() []*postgres.HostBasedAuthentication {
222205
// PgBouncer must connect over TLS using a SCRAM password. Other network
223206
// connections are forbidden.

internal/pgbouncer/reconcile.go

+20-3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ import (
1818
"github.com/crunchydata/postgres-operator/internal/naming"
1919
"github.com/crunchydata/postgres-operator/internal/pki"
2020
"github.com/crunchydata/postgres-operator/internal/postgres"
21+
passwd "github.com/crunchydata/postgres-operator/internal/postgres/password"
22+
"github.com/crunchydata/postgres-operator/internal/util"
2123
"github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1"
2224
)
2325

@@ -54,14 +56,29 @@ func Secret(ctx context.Context,
5456
var err error
5557
initialize.Map(&outSecret.Data)
5658

57-
// Use the existing password and verifier. Generate both when either is missing.
59+
// Use the existing password and verifier. Generate when one is missing.
60+
// PgBouncer can login to PostgreSQL using either MD5 or SCRAM-SHA-256.
61+
// When using MD5, the (hashed) verifier can be stored in PgBouncer's
62+
// authentication file. When using SCRAM, the plaintext password must be
63+
// stored.
64+
// - https://www.pgbouncer.org/config.html#authentication-file-format
65+
// - https://github.com/pgbouncer/pgbouncer/issues/508#issuecomment-713339834
5866
// NOTE(cbandy): We don't have a function to compare a plaintext password
5967
// to a SCRAM verifier.
6068
password := string(inSecret.Data[passwordSecretKey])
6169
verifier := string(inSecret.Data[verifierSecretKey])
6270

63-
if err == nil && (len(password) == 0 || len(verifier) == 0) {
64-
password, verifier, err = generatePassword()
71+
if len(password) == 0 {
72+
// If the password is empty, generate new password and verifier.
73+
password, err = util.GenerateASCIIPassword(32)
74+
err = errors.WithStack(err)
75+
if err == nil {
76+
verifier, err = passwd.NewSCRAMPassword(password).Build()
77+
err = errors.WithStack(err)
78+
}
79+
} else if len(password) != 0 && len(verifier) == 0 {
80+
// If the password is non-empty and the verifier is empty, generate a new verifier.
81+
verifier, err = passwd.NewSCRAMPassword(password).Build()
6582
err = errors.WithStack(err)
6683
}
6784

internal/pgbouncer/reconcile_test.go

+47
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,53 @@ func TestSecret(t *testing.T) {
9191
assert.DeepEqual(t, before, intent)
9292
}
9393

94+
func TestSCRAMVerifier(t *testing.T) {
95+
t.Parallel()
96+
97+
ctx := context.Background()
98+
cluster := new(v1beta1.PostgresCluster)
99+
service := new(corev1.Service)
100+
existing := new(corev1.Secret)
101+
intent := new(corev1.Secret)
102+
103+
root, err := pki.NewRootCertificateAuthority()
104+
assert.NilError(t, err)
105+
106+
cluster.Spec.Proxy = new(v1beta1.PostgresProxySpec)
107+
cluster.Spec.Proxy.PGBouncer = new(v1beta1.PGBouncerPodSpec)
108+
cluster.Default()
109+
110+
// Simulate the setting of a password only
111+
existing.Data = map[string][]byte{
112+
"pgbouncer-password": []byte("password"),
113+
}
114+
115+
// Verify that a SCRAM verifier is set
116+
assert.NilError(t, Secret(ctx, cluster, root, existing, service, intent))
117+
assert.Assert(t, len(intent.Data["pgbouncer-verifier"]) != 0)
118+
119+
// Simulate the setting of a password and a verifier
120+
intent = new(corev1.Secret)
121+
existing.Data = map[string][]byte{
122+
"pgbouncer-verifier": []byte("SCRAM-SHA-256$4096:randomsalt:storedkey:serverkey"),
123+
"pgbouncer-password": []byte("password"),
124+
}
125+
assert.NilError(t, Secret(ctx, cluster, root, existing, service, intent))
126+
assert.Equal(t, string(intent.Data["pgbouncer-verifier"]), "SCRAM-SHA-256$4096:randomsalt:storedkey:serverkey")
127+
assert.Equal(t, string(intent.Data["pgbouncer-password"]), "password")
128+
129+
// Simulate the setting of a verifier only
130+
intent = new(corev1.Secret)
131+
existing.Data = map[string][]byte{
132+
"pgbouncer-verifier": []byte("SCRAM-SHA-256$4096:randomsalt:storedkey:serverkey"),
133+
}
134+
assert.NilError(t, Secret(ctx, cluster, root, existing, service, intent))
135+
assert.Assert(t, string(intent.Data["pgbouncer-verifier"]) != "SCRAM-SHA-256$4096:randomsalt:storedkey:serverkey")
136+
assert.Assert(t, len(intent.Data["pgbouncer-password"]) != 0)
137+
assert.Assert(t, len(intent.Data["pgbouncer-verifier"]) != 0)
138+
139+
}
140+
94141
func TestPod(t *testing.T) {
95142
t.Parallel()
96143

0 commit comments

Comments
 (0)