Skip to content

Commit 8c6de56

Browse files
authored
Merge pull request #1081 from catj-cockroach/add-kubernetes-secret-support
adds support for kubernetes mounted private keys
2 parents 54a3a4b + d8917fa commit 8c6de56

File tree

3 files changed

+189
-6
lines changed

3 files changed

+189
-6
lines changed

conn.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -31,8 +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")
35-
ErrCouldNotDetectUsername = errors.New("pq: Could not detect default username. Please provide one explicitly")
34+
ErrSSLKeyUnknownOwnership = errors.New("pq: Could not get owner information for private key, may not be properly protected")
35+
ErrSSLKeyHasWorldPermissions = errors.New("pq: Private key has world access. Permissions should be u=rw,g=r (0640) if owned by root, or u=rw (0600), or less")
36+
37+
ErrCouldNotDetectUsername = errors.New("pq: Could not detect default username. Please provide one explicitly")
3638

3739
errUnexpectedReady = errors.New("unexpected ReadyForQuery")
3840
errNoRowsAffected = errors.New("no RowsAffected available after the empty statement")

ssl_permissions.go

+76-4
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,28 @@
33

44
package pq
55

6-
import "os"
6+
import (
7+
"errors"
8+
"os"
9+
"syscall"
10+
)
11+
12+
const (
13+
rootUserID = uint32(0)
14+
15+
// The maximum permissions that a private key file owned by a regular user
16+
// is allowed to have. This translates to u=rw.
17+
maxUserOwnedKeyPermissions os.FileMode = 0600
18+
19+
// The maximum permissions that a private key file owned by root is allowed
20+
// to have. This translates to u=rw,g=r.
21+
maxRootOwnedKeyPermissions os.FileMode = 0640
22+
)
23+
24+
var (
25+
errSSLKeyHasUnacceptableUserPermissions = errors.New("permissions for files not owned by root should be u=rw (0600) or less")
26+
errSSLKeyHasUnacceptableRootPermissions = errors.New("permissions for root owned files should be u=rw,g=r (0640) or less")
27+
)
728

829
// sslKeyPermissions checks the permissions on user-supplied ssl key files.
930
// The key file should have very little access.
@@ -14,8 +35,59 @@ func sslKeyPermissions(sslkey string) error {
1435
if err != nil {
1536
return err
1637
}
17-
if info.Mode().Perm()&0077 != 0 {
18-
return ErrSSLKeyHasWorldPermissions
38+
39+
err = hasCorrectPermissions(info)
40+
41+
// return ErrSSLKeyHasWorldPermissions for backwards compatability with
42+
// existing code.
43+
if err == errSSLKeyHasUnacceptableUserPermissions || err == errSSLKeyHasUnacceptableRootPermissions {
44+
err = ErrSSLKeyHasWorldPermissions
1945
}
20-
return nil
46+
return err
47+
}
48+
49+
// hasCorrectPermissions checks the file info (and the unix-specific stat_t
50+
// output) to verify that the permissions on the file are correct.
51+
//
52+
// If the file is owned by the same user the process is running as,
53+
// the file should only have 0600 (u=rw). If the file is owned by root,
54+
// and the group matches the group that the process is running in, the
55+
// permissions cannot be more than 0640 (u=rw,g=r). The file should
56+
// never have world permissions.
57+
//
58+
// Returns an error when the permission check fails.
59+
func hasCorrectPermissions(info os.FileInfo) error {
60+
// if file's permission matches 0600, allow access.
61+
userPermissionMask := (os.FileMode(0777) ^ maxUserOwnedKeyPermissions)
62+
63+
// regardless of if we're running as root or not, 0600 is acceptable,
64+
// so we return if we match the regular user permission mask.
65+
if info.Mode().Perm()&userPermissionMask == 0 {
66+
return nil
67+
}
68+
69+
// We need to pull the Unix file information to get the file's owner.
70+
// If we can't access it, there's some sort of operating system level error
71+
// and we should fail rather than attempting to use faulty information.
72+
sysInfo := info.Sys()
73+
if sysInfo == nil {
74+
return ErrSSLKeyUnknownOwnership
75+
}
76+
77+
unixStat, ok := sysInfo.(*syscall.Stat_t)
78+
if !ok {
79+
return ErrSSLKeyUnknownOwnership
80+
}
81+
82+
// if the file is owned by root, we allow 0640 (u=rw,g=r) to match what
83+
// Postgres does.
84+
if unixStat.Uid == rootUserID {
85+
rootPermissionMask := (os.FileMode(0777) ^ maxRootOwnedKeyPermissions)
86+
if info.Mode().Perm()&rootPermissionMask != 0 {
87+
return errSSLKeyHasUnacceptableRootPermissions
88+
}
89+
return nil
90+
}
91+
92+
return errSSLKeyHasUnacceptableUserPermissions
2193
}

ssl_permissions_test.go

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

0 commit comments

Comments
 (0)