Skip to content

Commit 65bf9f9

Browse files
adds support for kubernetes mounted private keys
1 parent 006a3f4 commit 65bf9f9

File tree

3 files changed

+173
-5
lines changed

3 files changed

+173
-5
lines changed

conn.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@ var (
3131
ErrNotSupported = errors.New("pq: Unsupported command")
3232
ErrInFailedTransaction = errors.New("pq: Could not complete operation in a failed transaction")
3333
ErrSSLNotSupported = errors.New("pq: SSL is not enabled on the server")
34-
ErrSSLKeyHasWorldPermissions = errors.New("pq: Private key file has group or world access. Permissions should be u=rw (0600) or less")
34+
ErrSSLKeyUnknownOwnership = errors.New("pq: Could not get owner information for private key, may not be properly protected.")
35+
ErrSSLKeyNotOwnedByGroup = errors.New("pq: Private key is owned by root and a group that doesn't match the process group ID.")
36+
ErrSSLKeyHasGroupPermissions = errors.New("pq: Private key file has group or world access. Permissions should be u=rw (0600) or less")
37+
ErrSSLKeyHasWorldPermissions = errors.New("pq: Private key owned by root has world access. Permissions should be u=rw (0660) or less")
3538
ErrCouldNotDetectUsername = errors.New("pq: Could not detect default username. Please provide one explicitly")
3639

3740
errUnexpectedReady = errors.New("unexpected ReadyForQuery")

ssl_permissions.go

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@
33

44
package pq
55

6-
import "os"
6+
import (
7+
"io/fs"
8+
"os"
9+
"syscall"
10+
)
11+
12+
const rootUserID = uint32(0)
713

814
// sslKeyPermissions checks the permissions on user-supplied ssl key files.
915
// The key file should have very little access.
@@ -14,8 +20,49 @@ func sslKeyPermissions(sslkey string) error {
1420
if err != nil {
1521
return err
1622
}
17-
if info.Mode().Perm()&0077 != 0 {
18-
return ErrSSLKeyHasWorldPermissions
23+
24+
return hasCorrectPermissions(info)
25+
}
26+
27+
// hasCorrectPermissions checks the file info (and the unix-specific stat_t
28+
// output) to verify that the permissions on the file are correct.
29+
//
30+
// If the file is owned by the same user the process is running as,
31+
// the file should only have 0600 (u=rw). If the file is owned by root,
32+
// and the group matches the group that the process is running in, the
33+
// permissions cannot be more than 0660 (u=rw,g=rw). The file should
34+
// never have world permissions.
35+
//
36+
// Returns an error when the permission check fails.
37+
func hasCorrectPermissions(info fs.FileInfo) error {
38+
// if file's permission matches 0600, allow access.
39+
if info.Mode().Perm()&0177 == 0 {
40+
return nil
1941
}
20-
return nil
42+
43+
sysInfo := info.Sys()
44+
if sysInfo == nil {
45+
return ErrSSLKeyUnknownOwnership
46+
}
47+
48+
unixStat, ok := sysInfo.(*syscall.Stat_t)
49+
if !ok {
50+
return ErrSSLKeyUnknownOwnership
51+
}
52+
53+
// if the file is owned by root, allow permission to be managed by
54+
// the process group ID.
55+
if unixStat.Uid == rootUserID {
56+
if unixStat.Gid != uint32(os.Getgid()) {
57+
return ErrSSLKeyNotOwnedByGroup
58+
}
59+
if info.Mode().Perm()&0117 != 0 {
60+
return ErrSSLKeyHasWorldPermissions
61+
}
62+
// if the file group ID matches th process group ID and the
63+
// file is not world readable, we are good to go
64+
return nil
65+
}
66+
67+
return ErrSSLKeyHasGroupPermissions
2168
}

ssl_permissions_test.go

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
//go:build !windows
2+
// +build !windows
3+
4+
package pq
5+
6+
import (
7+
"io/fs"
8+
"os"
9+
"syscall"
10+
"testing"
11+
"time"
12+
)
13+
14+
type stat_t_wrapper struct {
15+
stat syscall.Stat_t
16+
}
17+
18+
func (stat_t *stat_t_wrapper) Name() string {
19+
return "pem.key"
20+
}
21+
22+
func (stat_t *stat_t_wrapper) Size() int64 {
23+
return int64(100)
24+
}
25+
26+
func (stat_t *stat_t_wrapper) Mode() fs.FileMode {
27+
return fs.FileMode(stat_t.stat.Mode)
28+
}
29+
30+
func (stat_t *stat_t_wrapper) ModTime() time.Time {
31+
return time.Now()
32+
}
33+
34+
func (stat_t *stat_t_wrapper) IsDir() bool {
35+
return true
36+
}
37+
38+
func (stat_t *stat_t_wrapper) Sys() interface{} {
39+
return &stat_t.stat
40+
}
41+
42+
func TestHasCorrectRootGroupPermissions(t *testing.T) {
43+
currentUID := uint32(os.Getuid())
44+
currentGID := uint32(os.Getgid())
45+
46+
testData := []struct {
47+
expectedError error
48+
stat syscall.Stat_t
49+
}{
50+
{
51+
expectedError: nil,
52+
stat: syscall.Stat_t{
53+
Mode: 0600,
54+
Uid: currentUID,
55+
Gid: currentGID,
56+
},
57+
},
58+
{
59+
expectedError: nil,
60+
stat: syscall.Stat_t{
61+
Mode: 0660,
62+
Uid: 0,
63+
Gid: currentGID,
64+
},
65+
},
66+
{
67+
expectedError: ErrSSLKeyNotOwnedByGroup,
68+
stat: syscall.Stat_t{
69+
Mode: 0660,
70+
Uid: 0,
71+
Gid: 0,
72+
},
73+
},
74+
{
75+
expectedError: ErrSSLKeyHasGroupPermissions,
76+
stat: syscall.Stat_t{
77+
Mode: 0666,
78+
Uid: currentUID,
79+
Gid: currentGID,
80+
},
81+
},
82+
{
83+
expectedError: ErrSSLKeyHasWorldPermissions,
84+
stat: syscall.Stat_t{
85+
Mode: 0666,
86+
Uid: 0,
87+
Gid: currentGID,
88+
},
89+
},
90+
}
91+
92+
for _, test := range testData {
93+
wrapper := &stat_t_wrapper{
94+
stat: test.stat,
95+
}
96+
97+
if test.expectedError != hasCorrectPermissions(wrapper) {
98+
if test.expectedError == nil {
99+
t.Errorf(
100+
"file owned by %d:%d with %s should not have failed check with error \"%s\"",
101+
test.stat.Uid,
102+
test.stat.Gid,
103+
wrapper.Mode(),
104+
hasCorrectPermissions(wrapper),
105+
)
106+
continue
107+
}
108+
t.Errorf(
109+
"file owned by %d:%d with %s, expected \"%s\", got \"%s\"",
110+
test.stat.Uid,
111+
test.stat.Gid,
112+
wrapper.Mode(),
113+
test.expectedError,
114+
hasCorrectPermissions(wrapper),
115+
)
116+
}
117+
}
118+
}

0 commit comments

Comments
 (0)