Skip to content

Commit 279433b

Browse files
committedAug 23, 2017
*: add ability to define custom pg_hba.conf entries
Add a new cluster spec option called `pgHBA` where users can define a custom list of pg_hba.conf entries. These entries will be added to the pg_hba.conf after all the stolon managed entries so we'll guarantee local connections from the keeper and replication connection between pg instances. These entries aren't validated by stolon so if any of them is wrong the postgres instance will fail to start of return a warning on reload. If no custom pg_hba.conf entries are provided then we'll use the current behavior of accepting all hosts for all dbs and users with md5 authentincation: ``` host all all 0.0.0.0/0 md5 host all all ::0/0 md5 ```
1 parent e0148be commit 279433b

File tree

8 files changed

+130
-42
lines changed

8 files changed

+130
-42
lines changed
 

‎cmd/keeper/keeper.go

+24-8
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"os"
2222
"os/signal"
2323
"path/filepath"
24+
"reflect"
2425
"strconv"
2526
"strings"
2627
"sync"
@@ -658,8 +659,7 @@ func (p *PostgresKeeper) Start() {
658659

659660
// TODO(sgotti) reconfigure the various configurations options
660661
// (RequestTimeout) after a changed cluster config
661-
pgParameters := make(common.Parameters)
662-
pgm := postgresql.NewManager(p.pgBinPath, p.dataDir, pgParameters, p.getLocalConnParams(), p.getLocalReplConnParams(), p.pgSUUsername, p.pgSUPassword, p.pgReplUsername, p.pgReplPassword, p.requestTimeout)
662+
pgm := postgresql.NewManager(p.pgBinPath, p.dataDir, p.getLocalConnParams(), p.getLocalReplConnParams(), p.pgSUUsername, p.pgSUPassword, p.pgReplUsername, p.pgReplPassword, p.requestTimeout)
663663
p.pgm = pgm
664664

665665
p.pgm.Stop(true)
@@ -885,7 +885,8 @@ func (p *PostgresKeeper) postgresKeeperSM(pctx context.Context) {
885885

886886
followersUIDs := db.Spec.Followers
887887

888-
prevPGParameters := pgm.GetParameters()
888+
pgm.SetHba(db.Spec.PGHBA)
889+
889890
var pgParameters common.Parameters
890891

891892
dbls := p.dbLocalState
@@ -1465,7 +1466,7 @@ func (p *PostgresKeeper) postgresKeeperSM(pctx context.Context) {
14651466
pgParameters = p.createPGParameters(db)
14661467

14671468
// Log synchronous replication changes
1468-
prevSyncStandbyNames := prevPGParameters["synchronous_standby_names"]
1469+
prevSyncStandbyNames := pgm.CurParameters()["synchronous_standby_names"]
14691470
syncStandbyNames := pgParameters["synchronous_standby_names"]
14701471
if db.Spec.SynchronousReplication {
14711472
if prevSyncStandbyNames != syncStandbyNames {
@@ -1477,17 +1478,32 @@ func (p *PostgresKeeper) postgresKeeperSM(pctx context.Context) {
14771478
}
14781479
}
14791480

1480-
if !pgParameters.Equals(prevPGParameters) {
1481+
needsReload := false
1482+
1483+
if !pgParameters.Equals(pgm.CurParameters()) {
14811484
log.Infow("postgres parameters changed, reloading postgres instance")
14821485
pgm.SetParameters(pgParameters)
1483-
if err := pgm.Reload(); err != nil {
1484-
log.Errorw("failed to reload postgres instance", err)
1485-
}
1486+
needsReload = true
14861487
} else {
14871488
// for tests
14881489
log.Infow("postgres parameters not changed")
14891490
}
14901491

1492+
if !reflect.DeepEqual(db.Spec.PGHBA, pgm.CurHba()) {
1493+
log.Infow("postgres hba entries changed, reloading postgres instance")
1494+
pgm.SetHba(db.Spec.PGHBA)
1495+
needsReload = true
1496+
} else {
1497+
// for tests
1498+
log.Infow("postgres hba entries not changed")
1499+
}
1500+
1501+
if needsReload {
1502+
if err := pgm.Reload(); err != nil {
1503+
log.Errorw("failed to reload postgres instance", err)
1504+
}
1505+
}
1506+
14911507
// If we are here, then all went well and we can update the db generation and save it locally
14921508
p.localStateMutex.Lock()
14931509
dbls.Generation = db.Generation

‎cmd/sentinel/sentinel.go

+1
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,7 @@ func (s *Sentinel) setDBSpecFromClusterSpec(cd *cluster.ClusterData) {
368368
db.Spec.SynchronousReplication = s.syncRepl(clusterSpec)
369369
db.Spec.UsePgrewind = *clusterSpec.UsePgrewind
370370
db.Spec.PGParameters = clusterSpec.PGParameters
371+
db.Spec.PGHBA = clusterSpec.PGHBA
371372
if db.Spec.FollowConfig != nil && db.Spec.FollowConfig.Type == cluster.FollowTypeExternal {
372373
db.Spec.FollowConfig.StandbySettings = clusterSpec.StandbySettings
373374
}

‎cmd/stolonctl/update.go

-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,6 @@ func update(cmd *cobra.Command, args []string) {
117117
if err != nil {
118118
die("failed to patch cluster spec: %v", err)
119119
}
120-
121120
} else {
122121
if err := json.Unmarshal(data, &newcs); err != nil {
123122
die("failed to unmarshal cluster spec: %v", err)

‎doc/README.md

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ We suggest that you first read the [Stolon Architecture and Requirements](archit
77
* [Cluster Specification](cluster_spec.md)
88
* [Cluster Initialization](initialization.md)
99
* [Setting instance parameters](postgres_parameters.md)
10+
* [Custom pg_hba.conf entries](custom_pg_hba_entries.md)
1011
* [Stolon Client](stolonctl.md)
1112
* Backup/Restore
1213
* [Point In Time Recovery](pitr.md)

‎doc/cluster_spec.md

+23-22
Large diffs are not rendered by default.

‎doc/custom_pg_hba_entries.md

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
## Setting custom pg_hba.conf entries
2+
3+
Stolon manages the pg_hba.conf file entries. The first rules are generated by stolon to permit local keeper connections and remote replication connections since these are needed to ensure the correct operation of the cluster.
4+
5+
Users can specify custom pg_hba.conf entries setting the [cluster_specification](cluster_spec.md) `pgHBA` option. It must be a list of string containing additional pg_hba.conf entries. They will be added to the pg_hba.conf generated by stolon.
6+
7+
Since clients connection will pass through the stolon-proxy the host part of the entries should match at least the stolon-proxies source addresses. For the same reason it's not possible to directly filter by client. If you have clients that requires different accesses you should use different set of stolon proxies for every kind of access.
8+
9+
**NOTE**: these lines aren't validated so if some of them are wrong postgres will refuse to start or, on reload, will log a warning and ignore the updated pg_hba.conf file. Stolon will just check that the string doesn't contain newlines characters.
10+
11+
By default, if no custom pg_hba entries are defined (clusterpsec pgHBA option is null, not an empty list), to keep backward compatibility, stolon will add two rules to permit tcp (both ipv4 and ipv6) connections from every host to all dbs and usernames with md5 password authentication:
12+
13+
```
14+
host all all 0.0.0.0/0 md5
15+
host all all ::0/0 md5
16+
```

‎pkg/cluster/cluster.go

+15-3
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,14 @@ package cluster
1616

1717
import (
1818
"encoding/json"
19+
"fmt"
1920
"reflect"
21+
"sort"
2022
"strings"
2123
"time"
2224

2325
"github.com/sorintlab/stolon/common"
2426

25-
"fmt"
26-
"sort"
27-
2827
"github.com/mitchellh/copystructure"
2928
)
3029

@@ -243,6 +242,9 @@ type ClusterSpec struct {
243242
StandbySettings *StandbySettings `json:"standbySettings,omitempty"`
244243
// Map of postgres parameters
245244
PGParameters PGParameters `json:"pgParameters,omitempty"`
245+
// Additional pg_hba.conf entries
246+
// we don't set omitempty since we want to distinguish between null or empty slice
247+
PGHBA []string `json:"pgHBA"`
246248
}
247249

248250
type ClusterStatus struct {
@@ -392,6 +394,13 @@ func (os *ClusterSpec) Validate() error {
392394
if s.InitMode == nil {
393395
return fmt.Errorf("initMode undefined")
394396
}
397+
// The unique validation we're doing on pgHBA entries is that they don't contain a newline character
398+
for _, e := range s.PGHBA {
399+
if strings.Contains(e, "\n") {
400+
return fmt.Errorf("pgHBA entries cannot contain newline characters")
401+
}
402+
}
403+
395404
switch *s.InitMode {
396405
case ClusterInitModeNew:
397406
if *s.Role == ClusterRoleStandby {
@@ -526,6 +535,9 @@ type DBSpec struct {
526535
PITRConfig *PITRConfig `json:"pitrConfig,omitempty"`
527536
// Map of postgres parameters
528537
PGParameters PGParameters `json:"pgParameters,omitempty"`
538+
// Additional pg_hba.conf entries
539+
// We don't set omitempty since we want to distinguish between null or empty slice
540+
PGHBA []string `json:"pgHBA"`
529541
// DB Role (master or standby)
530542
Role common.Role `json:"role,omitempty"`
531543
// FollowConfig when Role is "standby"

‎pkg/postgresql/postgresql.go

+50-8
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
slog "github.com/sorintlab/stolon/pkg/log"
3232

3333
_ "github.com/lib/pq"
34+
"github.com/mitchellh/copystructure"
3435
"golang.org/x/net/context"
3536
)
3637

@@ -45,6 +46,9 @@ type Manager struct {
4546
pgBinPath string
4647
dataDir string
4748
parameters common.Parameters
49+
hba []string
50+
curParameters common.Parameters
51+
curHba []string
4852
localConnParams ConnParams
4953
replConnParams ConnParams
5054
suUsername string
@@ -72,11 +76,12 @@ type InitConfig struct {
7276
DataChecksums bool
7377
}
7478

75-
func NewManager(pgBinPath string, dataDir string, parameters common.Parameters, localConnParams, replConnParams ConnParams, suUsername, suPassword, replUsername, replPassword string, requestTimeout time.Duration) *Manager {
79+
func NewManager(pgBinPath string, dataDir string, localConnParams, replConnParams ConnParams, suUsername, suPassword, replUsername, replPassword string, requestTimeout time.Duration) *Manager {
7680
return &Manager{
7781
pgBinPath: pgBinPath,
7882
dataDir: filepath.Join(dataDir, "postgres"),
79-
parameters: parameters,
83+
parameters: make(common.Parameters),
84+
curParameters: make(common.Parameters),
8085
replConnParams: replConnParams,
8186
localConnParams: localConnParams,
8287
suUsername: suUsername,
@@ -91,8 +96,32 @@ func (p *Manager) SetParameters(parameters common.Parameters) {
9196
p.parameters = parameters
9297
}
9398

94-
func (p *Manager) GetParameters() common.Parameters {
95-
return p.parameters
99+
func (p *Manager) CurParameters() common.Parameters {
100+
return p.curParameters
101+
}
102+
103+
func (p *Manager) SetHba(hba []string) {
104+
p.hba = hba
105+
}
106+
107+
func (p *Manager) CurHba() []string {
108+
return p.curHba
109+
}
110+
111+
func (p *Manager) UpdateCurParameters() {
112+
n, err := copystructure.Copy(p.parameters)
113+
if err != nil {
114+
panic(err)
115+
}
116+
p.curParameters = n.(common.Parameters)
117+
}
118+
119+
func (p *Manager) UpdateCurHba() {
120+
n, err := copystructure.Copy(p.hba)
121+
if err != nil {
122+
panic(err)
123+
}
124+
p.curHba = n.([]string)
96125
}
97126

98127
func (p *Manager) Init(initConfig *InitConfig) error {
@@ -221,6 +250,9 @@ func (p *Manager) start(args ...string) error {
221250
return fmt.Errorf("error: %v", err)
222251
}
223252

253+
p.UpdateCurParameters()
254+
p.UpdateCurHba()
255+
224256
// pg_ctl with -w will exit after the timeout and return 0 also if the
225257
// instance isn't accepting connection because already in recovery (usually
226258
// waiting for wals during a pitr or a pg_rewind)
@@ -280,6 +312,10 @@ func (p *Manager) Reload() error {
280312
if err := cmd.Run(); err != nil {
281313
return fmt.Errorf("error: %v", err)
282314
}
315+
316+
p.UpdateCurParameters()
317+
p.UpdateCurHba()
318+
283319
return nil
284320
}
285321

@@ -608,10 +644,16 @@ func (p *Manager) writePgHba() error {
608644
f.WriteString(fmt.Sprintf("host replication %s %s md5\n", p.replUsername, "0.0.0.0/0"))
609645
f.WriteString(fmt.Sprintf("host replication %s %s md5\n", p.replUsername, "::0/0"))
610646

611-
// By default accept connections for all databases and users with md5 auth
612-
// TODO(sgotti) Do not set this but let the user provide its pg_hba.conf file/entries
613-
f.WriteString("host all all 0.0.0.0/0 md5\n")
614-
f.WriteString("host all all ::0/0 md5\n")
647+
if p.hba != nil {
648+
for _, e := range p.hba {
649+
f.WriteString(e + "\n")
650+
}
651+
} else {
652+
// By default, if no custom pg_hba entries are provided, accept
653+
// connections for all databases and users with md5 auth
654+
f.WriteString("host all all 0.0.0.0/0 md5\n")
655+
f.WriteString("host all all ::0/0 md5\n")
656+
}
615657

616658
if err = os.Rename(f.Name(), filepath.Join(p.dataDir, "pg_hba.conf")); err != nil {
617659
os.Remove(f.Name())

0 commit comments

Comments
 (0)