Skip to content

Commit 23acb76

Browse files
committed
Always set Postgres "log_file_mode" parameter
We set it when OpenTelemetry is enabled, but group permissions are good on Kubernetes storage generally. This adds some more validation tests around Postgres logging parameters. Issue: PGO-2558
1 parent aa00067 commit 23acb76

File tree

6 files changed

+77
-8
lines changed

6 files changed

+77
-8
lines changed

internal/collector/postgres.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ func EnablePostgresLogging(
117117
for k, v := range postgres.LogRotation(retentionPeriod, "postgresql-", ".log") {
118118
outParameters.Add(k, v)
119119
}
120-
outParameters.Add("log_file_mode", "0660")
121120

122121
// Log in a timezone that the OpenTelemetry Collector will understand.
123122
outParameters.Add("log_timezone", "UTC")

internal/postgres/parameters.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@ func NewParameters() Parameters {
4848
// - https://www.postgresql.org/docs/current/auth-password.html
4949
parameters.Default.Add("password_encryption", "scram-sha-256")
5050

51+
// Pod "securityContext.fsGroup" ensures processes and filesystems agree on a GID;
52+
// use the same permissions for group and owner.
53+
// This allows every process in the pod to read Postgres log files.
54+
//
55+
// S_IRUSR, S_IWUSR: (0600) enable owner read and write permissions
56+
// S_IRGRP, S_IWGRP: (0060) enable group read and write permissions.
57+
//
58+
// PostgreSQL must be reloaded when changing this value.
59+
parameters.Mandatory.Add("log_file_mode", "0660")
60+
5161
return parameters
5262
}
5363

internal/postgres/parameters_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ func TestNewParameters(t *testing.T) {
1414
parameters := NewParameters()
1515

1616
assert.DeepEqual(t, parameters.Mandatory.AsMap(), map[string]string{
17+
"log_file_mode": "0660",
18+
1719
"ssl": "on",
1820
"ssl_ca_file": "/pgconf/tls/ca.crt",
1921
"ssl_cert_file": "/pgconf/tls/tls.crt",

internal/shell/paths.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"fmt"
1212
"io/fs"
1313
"path/filepath"
14+
"slices"
1415
"strings"
1516
)
1617

@@ -41,17 +42,15 @@ func CleanFileName(path string) string {
4142
// - https://pubs.opengroup.org/onlinepubs/9799919799/utilities/test.html
4243
// - https://pubs.opengroup.org/onlinepubs/9799919799/utilities/umask.html
4344
func MakeDirectories(base string, paths ...string) string {
44-
// Without any paths, return a command that succeeds when the base path
45-
// exists.
45+
// Without any paths, return a command that succeeds when the base path exists.
4646
if len(paths) == 0 {
4747
return `test -d ` + QuoteWord(base)
4848
}
4949

50-
allPaths := append([]string(nil), paths...)
50+
allPaths := slices.Clone(paths)
5151
for _, p := range paths {
5252
if r, err := filepath.Rel(base, p); err == nil && filepath.IsLocal(r) {
53-
// The result of [filepath.Rel] is a shorter representation
54-
// of the full path; skip it.
53+
// The result of [filepath.Rel] is a shorter representation of the full path; skip it.
5554
r = filepath.Dir(r)
5655

5756
for r != "." {
@@ -61,6 +60,8 @@ func MakeDirectories(base string, paths ...string) string {
6160
}
6261
}
6362

63+
// Pod "securityContext.fsGroup" ensures processes and filesystems agree on a GID.
64+
// Use the same permissions for group and owner.
6465
const perms fs.FileMode = 0 |
6566
// S_IRWXU: enable owner read, write, and execute permissions.
6667
0o0700 |

internal/shell/paths_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,4 +93,11 @@ func TestMakeDirectories(t *testing.T) {
9393
"expected plain unquoted scalar, got:\n%s", b)
9494
})
9595
})
96+
97+
t.Run("Unrelated", func(t *testing.T) {
98+
assert.Equal(t,
99+
MakeDirectories("/one", "/two/three/four"),
100+
`mkdir -p '/two/three/four' && { chmod 0775 '/two/three/four' || :; }`,
101+
"expected no chmod of parent directories")
102+
})
96103
}

internal/testing/validation/postgrescluster/postgres_config_test.go

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
package validation
66

77
import (
8+
"fmt"
89
"testing"
910

1011
"gotest.tools/v3/assert"
@@ -118,8 +119,6 @@ func testPostgresConfigParametersCommon(t *testing.T, cc client.Client, base uns
118119
{key: "hot_standby", value: "off"},
119120
{key: "ident_file", value: "two"},
120121
{key: "listen_addresses", value: ""},
121-
{key: "log_file_mode", value: ""},
122-
{key: "logging_collector", value: "off"},
123122
{key: "port", value: 5},
124123
{key: "wal_log_hints", value: "off"},
125124
} {
@@ -143,6 +142,57 @@ func testPostgresConfigParametersCommon(t *testing.T, cc client.Client, base uns
143142
}
144143
})
145144

145+
t.Run("Logging", func(t *testing.T) {
146+
for _, tt := range []struct {
147+
valid bool
148+
key string
149+
value any
150+
message string
151+
}{
152+
{valid: false, key: "log_file_mode", value: "", message: "cannot be changed"},
153+
{valid: false, key: "log_file_mode", value: "any", message: "cannot be changed"},
154+
{valid: false, key: "logging_collector", value: "", message: "unsafe"},
155+
{valid: false, key: "logging_collector", value: "off", message: "unsafe"},
156+
{valid: false, key: "logging_collector", value: "on", message: "unsafe"},
157+
158+
{valid: true, key: "log_destination", value: "anything"},
159+
{valid: true, key: "log_directory", value: "anything"},
160+
{valid: true, key: "log_filename", value: "anything"},
161+
{valid: true, key: "log_filename", value: "percent-%s-too"},
162+
{valid: true, key: "log_rotation_age", value: "7d"},
163+
{valid: true, key: "log_rotation_age", value: 5},
164+
{valid: true, key: "log_rotation_size", value: "100MB"},
165+
{valid: true, key: "log_rotation_size", value: 13},
166+
{valid: true, key: "log_timezone", value: ""},
167+
{valid: true, key: "log_timezone", value: "nonsense"},
168+
} {
169+
t.Run(fmt.Sprint(tt), func(t *testing.T) {
170+
cluster := base.DeepCopy()
171+
require.UnmarshalIntoField(t, cluster,
172+
require.Value(yaml.Marshal(tt.value)),
173+
"spec", "config", "parameters", tt.key)
174+
175+
err := cc.Create(ctx, cluster, client.DryRunAll)
176+
177+
if tt.valid {
178+
assert.NilError(t, err)
179+
assert.Equal(t, "", tt.message, "BUG IN TEST: no message expected when valid")
180+
} else {
181+
assert.Assert(t, apierrors.IsInvalid(err))
182+
183+
status := require.StatusError(t, err)
184+
assert.Assert(t, status.Details != nil)
185+
assert.Assert(t, cmp.Len(status.Details.Causes, 1))
186+
187+
// TODO(k8s-1.30) TODO(validation): Move the parameter name from the message to the field path.
188+
assert.Equal(t, status.Details.Causes[0].Field, "spec.config.parameters")
189+
assert.Assert(t, cmp.Contains(status.Details.Causes[0].Message, tt.key))
190+
assert.Assert(t, cmp.Contains(status.Details.Causes[0].Message, tt.message))
191+
}
192+
})
193+
}
194+
})
195+
146196
t.Run("NoConnections", func(t *testing.T) {
147197
for _, tt := range []struct {
148198
key string

0 commit comments

Comments
 (0)