Skip to content

Commit 9711fd1

Browse files
committed
initialize operator user from environment
1 parent 0a7fdd0 commit 9711fd1

File tree

3 files changed

+218
-3
lines changed

3 files changed

+218
-3
lines changed

cmd/start/main.go

+70-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package main
22

33
import (
44
"bytes"
5+
"context"
6+
"errors"
57
"fmt"
68
"os"
79
"os/signal"
@@ -12,7 +14,9 @@ import (
1214
"time"
1315

1416
"github.com/fly-examples/postgres-ha/pkg/flypg"
17+
"github.com/fly-examples/postgres-ha/pkg/flypg/admin"
1518
"github.com/fly-examples/postgres-ha/pkg/supervisor"
19+
"github.com/jackc/pgx/v4"
1620
)
1721

1822
func main() {
@@ -53,16 +57,29 @@ func main() {
5357
}
5458

5559
currentKeeper := cd.Keepers[node.KeeperUID]
56-
fmt.Printf("found keeper: %#v\n", currentKeeper)
60+
// fmt.Printf("found keeper: %#v\n", currentKeeper)
5761
currentDB := cd.FindDB(currentKeeper)
58-
fmt.Printf("found db: %#v\n", currentDB)
62+
// fmt.Printf("found db: %#v\n", currentDB)
5963

6064
if currentKeeper == nil || currentDB == nil {
6165
continue
6266
}
6367

64-
if currentKeeper.Status.Healthy && currentDB.Status.Healthy {
68+
if currentKeeper.Status.Healthy && currentDB.Status.Healthy && currentDB.Spec.Role == "master" {
6569
fmt.Println("keeper is healthy, db is healthy, role:", currentDB.Spec.Role)
70+
71+
pg, err := node.NewLocalConnection(context.TODO())
72+
if err != nil {
73+
fmt.Println("error connecting to local postgres", err)
74+
continue
75+
}
76+
77+
if err = initOperator(context.TODO(), pg, node.OperatorCredentials); err != nil {
78+
fmt.Println("error configuring operator:", err)
79+
continue
80+
}
81+
fmt.Println("operator ready!")
82+
break
6683
}
6784
}
6885
}()
@@ -153,3 +170,53 @@ func writeStolonctlEnvFile(n *flypg.Node, filename string) {
153170

154171
os.WriteFile(filename, b.Bytes(), 0644)
155172
}
173+
174+
func initOperator(ctx context.Context, pg *pgx.Conn, creds flypg.Credentials) error {
175+
fmt.Println("configuring operator")
176+
177+
users, err := admin.ListUsers(ctx, pg)
178+
if err != nil {
179+
return err
180+
}
181+
182+
var operatorUser *admin.UserInfo
183+
184+
for _, u := range users {
185+
if u.Username == creds.Username {
186+
operatorUser = &u
187+
break
188+
}
189+
}
190+
191+
if operatorUser == nil {
192+
fmt.Println("operator user does not exist, creating")
193+
err = admin.CreateUser(ctx, pg, creds.Username, creds.Password)
194+
if err != nil {
195+
return err
196+
}
197+
operatorUser, err = admin.FindUser(ctx, pg, creds.Username)
198+
if err != nil {
199+
return err
200+
}
201+
}
202+
203+
if operatorUser == nil {
204+
return errors.New("error creating operator: user not found")
205+
}
206+
207+
if !operatorUser.SuperUser {
208+
fmt.Println("operator is not a superuser, fixing")
209+
if err := admin.GrantSuperuser(ctx, pg, creds.Username); err != nil {
210+
return err
211+
}
212+
}
213+
214+
if !operatorUser.IsPassword(creds.Password) {
215+
fmt.Println("operator password does not match config, changing")
216+
if err := admin.ChangePassword(ctx, pg, creds.Username, creds.Password); err != nil {
217+
return err
218+
}
219+
}
220+
221+
return nil
222+
}

docker-compose.yml

+6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ services:
2424
- consul
2525
environment:
2626
FLY_CONSUL_URL: "http://consul:8500/chaos-postgres-wvo4x1opkz9l5ydn/"
27+
SU_USERNAME: "flypgadmin"
28+
SU_PASSWORD: "supassword"
29+
REPL_USERNAME: "repluser"
30+
REPL_PASSWORD: "replpassword"
31+
OPERATOR_USERNAME: "postgres"
32+
OPERATOR_PASSWORD: "operatorpassword"
2733
ports:
2834
- "5432:5432"
2935
- "5433:5433"

pkg/flypg/admin/admin.go

+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package admin
2+
3+
import (
4+
"context"
5+
"crypto/md5"
6+
"fmt"
7+
"strings"
8+
9+
"github.com/jackc/pgx/v4"
10+
)
11+
12+
func CreateUser(ctx context.Context, pg *pgx.Conn, username string, password string) error {
13+
sql := fmt.Sprintf(`CREATE USER %s WITH LOGIN PASSWORD '%s'`, username, password)
14+
15+
_, err := pg.Exec(ctx, sql)
16+
if err != nil {
17+
return err
18+
}
19+
20+
return nil
21+
}
22+
23+
func GrantSuperuser(ctx context.Context, pg *pgx.Conn, username string) error {
24+
sql := fmt.Sprintf("ALTER USER %s WITH SUPERUSER;", username)
25+
26+
_, err := pg.Exec(ctx, sql)
27+
if err != nil {
28+
return err
29+
}
30+
31+
return nil
32+
}
33+
34+
func ChangePassword(ctx context.Context, pg *pgx.Conn, username, password string) error {
35+
sql := fmt.Sprintf("ALTER USER %s WITH LOGIN PASSWORD '%s';", username, password)
36+
37+
_, err := pg.Exec(ctx, sql)
38+
if err != nil {
39+
return err
40+
}
41+
42+
return nil
43+
}
44+
45+
func ListDatabases(ctx context.Context, pg *pgx.Conn) ([]DbInfo, error) {
46+
sql := `
47+
SELECT d.datname,
48+
(SELECT array_agg(u.usename::text order by u.usename)
49+
from pg_user u
50+
where has_database_privilege(u.usename, d.datname, 'CONNECT')) as allowed_users
51+
from pg_database d where d.datistemplate = false
52+
order by d.datname;
53+
`
54+
55+
rows, err := pg.Query(ctx, sql)
56+
if err != nil {
57+
return nil, err
58+
}
59+
defer rows.Close()
60+
61+
values := []DbInfo{}
62+
63+
for rows.Next() {
64+
di := DbInfo{}
65+
if err := rows.Scan(&di.Name, &di.Users); err != nil {
66+
return nil, err
67+
}
68+
values = append(values, di)
69+
}
70+
71+
return values, nil
72+
}
73+
74+
type UserInfo struct {
75+
Username string `json:"username"`
76+
SuperUser bool `json:"superuser"`
77+
Databases []string `json:"databases"`
78+
PasswordHash string `json:"-"`
79+
}
80+
81+
func (ui UserInfo) IsPassword(password string) bool {
82+
if !strings.HasPrefix(ui.PasswordHash, "md5") {
83+
return false
84+
}
85+
86+
encoded := fmt.Sprintf("md5%x", md5.Sum([]byte(password+ui.Username)))
87+
return encoded == ui.PasswordHash
88+
}
89+
90+
type DbInfo struct {
91+
Name string `json:"name"`
92+
Users []string `json:"users"`
93+
}
94+
95+
func ListUsers(ctx context.Context, pg *pgx.Conn) ([]UserInfo, error) {
96+
sql := `
97+
select u.usename,
98+
usesuper as superuser,
99+
a.rolpassword as passwordhash,
100+
(select array_agg(d.datname::text order by d.datname)
101+
from pg_database d
102+
WHERE datistemplate = false
103+
AND has_database_privilege(u.usename, d.datname, 'CONNECT')
104+
) as allowed_databases
105+
from pg_user u
106+
join pg_authid a on u.usesysid = a.oid
107+
order by u.usename
108+
`
109+
110+
rows, err := pg.Query(ctx, sql)
111+
if err != nil {
112+
return nil, err
113+
}
114+
defer rows.Close()
115+
116+
values := []UserInfo{}
117+
118+
for rows.Next() {
119+
ui := UserInfo{}
120+
if err := rows.Scan(&ui.Username, &ui.SuperUser, &ui.PasswordHash, &ui.Databases); err != nil {
121+
return nil, err
122+
}
123+
values = append(values, ui)
124+
}
125+
126+
return values, nil
127+
}
128+
129+
func FindUser(ctx context.Context, pg *pgx.Conn, username string) (*UserInfo, error) {
130+
users, err := ListUsers(ctx, pg)
131+
if err != nil {
132+
return nil, err
133+
}
134+
135+
for _, u := range users {
136+
if u.Username == username {
137+
return &u, nil
138+
}
139+
}
140+
141+
return nil, nil
142+
}

0 commit comments

Comments
 (0)