Skip to content

Commit 6e7bde7

Browse files
committed
sockets: implement WithAdditionalUsersAndGroups for windows
- Implement a WithAdditionalUsersAndGroups (windows daemon allows specifying multiple additional users and groups for named pipes and unix-sockets). - Implement a WithBasePermissions() option for windows - Implement NewUnixSocket that accepts (optional) additional users and groups. Signed-off-by: Sebastiaan van Stijn <[email protected]>
1 parent 9ffab7e commit 6e7bde7

File tree

3 files changed

+147
-4
lines changed

3 files changed

+147
-4
lines changed

go.mod

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module github.com/docker/go-connections
22

33
go 1.18
44

5-
require github.com/Microsoft/go-winio v0.4.21
6-
7-
require golang.org/x/sys v0.1.0 // indirect
5+
require (
6+
github.com/Microsoft/go-winio v0.4.21
7+
golang.org/x/sys v0.1.0
8+
)

sockets/unix_socket_windows.go

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,126 @@
11
package sockets
22

3-
import "net"
3+
import (
4+
"errors"
5+
"fmt"
6+
"net"
7+
"strings"
8+
9+
"github.com/Microsoft/go-winio"
10+
"golang.org/x/sys/windows"
11+
)
12+
13+
// BasePermissions defines the default DACL, which allows Administrators
14+
// and LocalSystem full access (similar to defaults used in [moby]);
15+
//
16+
// - D:P: DACL without inheritance (protected, (P)).
17+
// - (A;;GA;;;BA): Allow full access (GA) for built-in Administrators (BA).
18+
// - (A;;GA;;;SY); Allow full access (GA) for LocalSystem (SY).
19+
// - Any other user is denied access.
20+
//
21+
// [moby]: https://github.com/moby/moby/blob/6b45c76a233b1b8b56465f76c21c09fd7920e82d/daemon/listeners/listeners_windows.go#L53-L59
22+
const BasePermissions = "D:P(A;;GA;;;BA)(A;;GA;;;SY)"
23+
24+
// WithBasePermissions sets a default DACL, which allows Administrators
25+
// and LocalSystem full access (similar to defaults used in [moby]);
26+
//
27+
// - D:P: DACL without inheritance (protected, (P)).
28+
// - (A;;GA;;;BA): Allow full access (GA) for built-in Administrators (BA).
29+
// - (A;;GA;;;SY); Allow full access (GA) for LocalSystem (SY).
30+
// - Any other user is denied access.
31+
//
32+
// [moby]: https://github.com/moby/moby/blob/6b45c76a233b1b8b56465f76c21c09fd7920e82d/daemon/listeners/listeners_windows.go#L53-L59
33+
func WithBasePermissions() SockOption {
34+
return withSDDL(BasePermissions)
35+
}
36+
37+
// WithAdditionalUsersAndGroups modifies the socket file's DACL to grant
38+
// access to additional users and groups.
39+
//
40+
// It sets [BasePermissions] on the socket path and grants the given additional
41+
// users and groups to generic read (GR) and write (GW) access. It returns
42+
// an error if no groups were given, when failing to resolve any of the
43+
// additional users and groups, or when failing to apply the ACL.
44+
func WithAdditionalUsersAndGroups(additionalUsersAndGroups []string) SockOption {
45+
return func(path string) error {
46+
if len(additionalUsersAndGroups) == 0 {
47+
return errors.New("no additional users specified")
48+
}
49+
sd, err := getSecurityDescriptor(additionalUsersAndGroups)
50+
if err != nil {
51+
return fmt.Errorf("looking up SID: %w", err)
52+
}
53+
return withSDDL(sd)(path)
54+
}
55+
}
56+
57+
// withSDDL applies the given SDDL to the socket. It returns an error
58+
// when failing parse the SDDL, or if the DACL was defaulted.
59+
//
60+
// TODO(thaJeztah); this is not exported yet, as some of the checks may need review if they're not too opinionated.
61+
func withSDDL(sddl string) SockOption {
62+
return func(path string) error {
63+
sd, err := windows.SecurityDescriptorFromString(sddl)
64+
if err != nil {
65+
return fmt.Errorf("parsing SDDL: %w", err)
66+
}
67+
dacl, defaulted, err := sd.DACL()
68+
if err != nil {
69+
return fmt.Errorf("extracting DACL: %w", err)
70+
}
71+
if dacl == nil || defaulted {
72+
// should never be hit with our [DefaultPermissions],
73+
// as it contains "D:" and "P" (protected, don't inherit).
74+
return errors.New("no DACL found in security descriptor or defaulted")
75+
}
76+
return windows.SetNamedSecurityInfo(
77+
path,
78+
windows.SE_FILE_OBJECT,
79+
windows.DACL_SECURITY_INFORMATION|windows.OWNER_SECURITY_INFORMATION,
80+
nil, // do not change the owner
81+
nil, // do not change the owner
82+
dacl,
83+
nil,
84+
)
85+
}
86+
}
87+
88+
// NewUnixSocket creates a new unix socket.
89+
//
90+
// It sets [BasePermissions] on the socket path and grants the given additional
91+
// users and groups to generic read (GR) and write (GW) access. It returns
92+
// an error when failing to resolve any of the additional users and groups,
93+
// or when failing to apply the ACL.
94+
func NewUnixSocket(path string, additionalUsersAndGroups []string) (net.Listener, error) {
95+
var opts []SockOption
96+
if len(additionalUsersAndGroups) > 0 {
97+
opts = append(opts, WithAdditionalUsersAndGroups(additionalUsersAndGroups))
98+
} else {
99+
opts = append(opts, WithBasePermissions())
100+
}
101+
return NewUnixSocketWithOpts(path, opts...)
102+
}
103+
104+
// getSecurityDescriptor returns the DACL for the Unix socket.
105+
//
106+
// By default, it grants [BasePermissions], but allows for additional
107+
// users and groups to get generic read (GR) and write (GW) access. It
108+
// returns an error when failing to resolve any of the additional users
109+
// and groups.
110+
func getSecurityDescriptor(additionalUsersAndGroups []string) (string, error) {
111+
sddl := BasePermissions
112+
113+
// Grant generic read (GR) and write (GW) access to whatever
114+
// additional users or groups were specified.
115+
for _, g := range additionalUsersAndGroups {
116+
sid, err := winio.LookupSidByName(strings.TrimSpace(g))
117+
if err != nil {
118+
return "", fmt.Errorf("looking up SID: %w", err)
119+
}
120+
sddl += fmt.Sprintf("(A;;GRGW;;;%s)", sid)
121+
}
122+
return sddl, nil
123+
}
4124

5125
func listenUnix(path string) (net.Listener, error) {
6126
return net.Listen("unix", path)

sockets/unix_socket_windows_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,25 @@ func TestUnixSocketWithOpts(t *testing.T) {
2222
echoStr := "hello"
2323
runTest(t, socketFile.Name(), l, echoStr)
2424
}
25+
26+
func TestNewUnixSocket(t *testing.T) {
27+
group := "Users" // for testing, should always be available
28+
path := "/tmp/test.sock"
29+
echoStr := "hello"
30+
l, err := NewUnixSocket(path, []string{group})
31+
if err != nil {
32+
t.Fatal(err)
33+
}
34+
defer func() { _ = l.Close() }()
35+
runTest(t, path, l, echoStr)
36+
}
37+
38+
func TestNewUnixSocketUnknownGroup(t *testing.T) {
39+
group := "NoSuchUserOrGroup"
40+
path := "/tmp/fail.sock"
41+
_, err := NewUnixSocket(path, []string{group})
42+
if err == nil {
43+
t.Errorf("expected error, got nil")
44+
}
45+
_ = os.Remove(path)
46+
}

0 commit comments

Comments
 (0)