|
1 | 1 | package sockets |
2 | 2 |
|
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 | +} |
4 | 124 |
|
5 | 125 | func listenUnix(path string) (net.Listener, error) { |
6 | 126 | return net.Listen("unix", path) |
|
0 commit comments