|
| 1 | +// Copyright 2021 - 2025 Crunchy Data Solutions, Inc. |
| 2 | +// |
| 3 | +// SPDX-License-Identifier: Apache-2.0 |
| 4 | + |
| 5 | +package postgres |
| 6 | + |
| 7 | +import ( |
| 8 | + "path" |
| 9 | + "regexp" |
| 10 | + "strings" |
| 11 | + |
| 12 | + "github.com/crunchydata/postgres-operator/pkg/apis/postgres-operator.crunchydata.com/v1beta1" |
| 13 | +) |
| 14 | + |
| 15 | +// SanitizeParameters transforms parameters so they are safe for Postgres in cluster. |
| 16 | +func SanitizeParameters(cluster *v1beta1.PostgresCluster, parameters *ParameterSet) { |
| 17 | + if v, ok := parameters.Get("log_directory"); ok { |
| 18 | + parameters.Add("log_directory", sanitizeLogDirectory(cluster, v)) |
| 19 | + } |
| 20 | +} |
| 21 | + |
| 22 | +// sensitiveAbsolutePath matches absolute paths that Postgres expects to control. |
| 23 | +// User input should not direct tools to write to these directories. |
| 24 | +// |
| 25 | +// See [sanitizeLogDirectory]. |
| 26 | +var sensitiveAbsolutePath = regexp.MustCompile( |
| 27 | + // Rooted in one of these volumes |
| 28 | + `^(` + dataMountPath + `|` + tmpMountPath + `|` + walMountPath + `)` + |
| 29 | + |
| 30 | + // First subdirectory is a Postgres directory |
| 31 | + `/(` + `pg\d+` + // [DataDirectory] |
| 32 | + `|` + `pgsql_tmp` + // https://www.postgresql.org/docs/current/storage-file-layout.html |
| 33 | + `|` + `pg\d+_wal` + // [WALDirectory] |
| 34 | + `)(/|$)`, |
| 35 | +) |
| 36 | + |
| 37 | +// sensitiveRelativePath matches paths relative to the Postgres "data_directory" that Postgres expects to control. |
| 38 | +// Arguably, everything inside the data directory is sensitve, but this is here because |
| 39 | +// Postges interprets some of its parameters relative to its data directory. |
| 40 | +// |
| 41 | +// User input should not direct tools to write to these directories. |
| 42 | +// |
| 43 | +// NOTE: This is not an exhaustive list! New code should use an allowlist rather than this denylist. |
| 44 | +// |
| 45 | +// See [sanitizeLogDirectory]. |
| 46 | +var sensitiveRelativePath = regexp.MustCompile( |
| 47 | + `^(archive|base|current|global|patroni|pg_|PG_|postgresql|postmaster|[[:xdigit:]]{24,})` + |
| 48 | + `|` + `[.](history|partial)$`, |
| 49 | +) |
| 50 | + |
| 51 | +// sanitizeLogDirectory returns the absolute path to input when it is a safe "log_directory" for cluster. |
| 52 | +// Otherwise, it returns the absolute path to a good "log_directory" value. |
| 53 | +// |
| 54 | +// https://www.postgresql.org/docs/current/runtime-config-logging.html#GUC-LOG-DIRECTORY |
| 55 | +func sanitizeLogDirectory(cluster *v1beta1.PostgresCluster, input string) string { |
| 56 | + directory := path.Clean(input) |
| 57 | + |
| 58 | + // [path.Clean] leaves leading parent directories. Eliminate these as a security measure. |
| 59 | + for strings.HasPrefix(directory, "../") { |
| 60 | + directory = directory[3:] |
| 61 | + } |
| 62 | + |
| 63 | + switch { |
| 64 | + case directory == "log": |
| 65 | + // This the Postgres default and the only relative path allowed in v1 of PostgresCluster. |
| 66 | + // Expand it relative to the data directory like Postgres does. |
| 67 | + return path.Join(DataDirectory(cluster), "log") |
| 68 | + |
| 69 | + case directory == "", directory == ".", directory == "/", |
| 70 | + sensitiveAbsolutePath.MatchString(directory), |
| 71 | + sensitiveRelativePath.MatchString(directory): |
| 72 | + // When the value is empty after cleaning or disallowed, choose one instead. |
| 73 | + // Keep it on the same volume, if possible. |
| 74 | + if strings.HasPrefix(directory, tmpMountPath) { |
| 75 | + return path.Join(tmpMountPath, "logs/postgres") |
| 76 | + } |
| 77 | + if strings.HasPrefix(directory, walMountPath) { |
| 78 | + return path.Join(walMountPath, "logs/postgres") |
| 79 | + } |
| 80 | + |
| 81 | + // There is always a data volume, so use that. |
| 82 | + return path.Join(dataMountPath, "logs/postgres") |
| 83 | + |
| 84 | + case !path.IsAbs(directory): |
| 85 | + // Directory is relative. This is disallowed since v1 of PostgresCluster. |
| 86 | + // Expand it relative to the data directory like Postgres does. |
| 87 | + return path.Join(DataDirectory(cluster), directory) |
| 88 | + |
| 89 | + default: |
| 90 | + // Directory is absolute and considered safe; use it. |
| 91 | + return directory |
| 92 | + } |
| 93 | +} |
0 commit comments