vendor: github.com/docker/go-connections v0.7.0
full diff: https://github.com/docker/go-connections/compare/v0.6.0...v0.7.0 Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
This commit is contained in:
+36
-36
@@ -27,19 +27,15 @@ type PortSet map[Port]struct{}
|
||||
type Port string
|
||||
|
||||
// NewPort creates a new instance of a Port given a protocol and port number or port range
|
||||
func NewPort(proto, port string) (Port, error) {
|
||||
// Check for parsing issues on "port" now so we can avoid having
|
||||
// to check it later on.
|
||||
|
||||
portStartInt, portEndInt, err := ParsePortRangeToInt(port)
|
||||
func NewPort(proto, portOrRange string) (Port, error) {
|
||||
start, end, err := parsePortRange(portOrRange)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if portStartInt == portEndInt {
|
||||
return Port(fmt.Sprintf("%d/%s", portStartInt, proto)), nil
|
||||
if start == end {
|
||||
return Port(fmt.Sprintf("%d/%s", start, proto)), nil
|
||||
}
|
||||
return Port(fmt.Sprintf("%d-%d/%s", portStartInt, portEndInt, proto)), nil
|
||||
return Port(fmt.Sprintf("%d-%d/%s", start, end, proto)), nil
|
||||
}
|
||||
|
||||
// ParsePort parses the port number string and returns an int
|
||||
@@ -47,49 +43,53 @@ func ParsePort(rawPort string) (int, error) {
|
||||
if rawPort == "" {
|
||||
return 0, nil
|
||||
}
|
||||
port, err := strconv.ParseUint(rawPort, 10, 16)
|
||||
port, err := parsePortNumber(rawPort)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid port '%s': %w", rawPort, errors.Unwrap(err))
|
||||
return 0, fmt.Errorf("invalid port '%s': %w", rawPort, err)
|
||||
}
|
||||
return int(port), nil
|
||||
return port, nil
|
||||
}
|
||||
|
||||
// ParsePortRangeToInt parses the port range string and returns start/end ints
|
||||
func ParsePortRangeToInt(rawPort string) (int, int, error) {
|
||||
func ParsePortRangeToInt(rawPort string) (startPort, endPort int, _ error) {
|
||||
if rawPort == "" {
|
||||
// TODO(thaJeztah): consider making this an error; this was kept to keep existing behavior.
|
||||
return 0, 0, nil
|
||||
}
|
||||
start, end, err := ParsePortRange(rawPort)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
return int(start), int(end), nil
|
||||
return parsePortRange(rawPort)
|
||||
}
|
||||
|
||||
// Proto returns the protocol of a Port
|
||||
func (p Port) Proto() string {
|
||||
proto, _ := SplitProtoPort(string(p))
|
||||
_, proto, _ := strings.Cut(string(p), "/")
|
||||
if proto == "" {
|
||||
proto = "tcp"
|
||||
}
|
||||
return proto
|
||||
}
|
||||
|
||||
// Port returns the port number of a Port
|
||||
func (p Port) Port() string {
|
||||
_, port := SplitProtoPort(string(p))
|
||||
port, _, _ := strings.Cut(string(p), "/")
|
||||
return port
|
||||
}
|
||||
|
||||
// Int returns the port number of a Port as an int
|
||||
// Int returns the port number of a Port as an int. It assumes [Port]
|
||||
// is valid, and returns 0 otherwise.
|
||||
func (p Port) Int() int {
|
||||
portStr := p.Port()
|
||||
// We don't need to check for an error because we're going to
|
||||
// assume that any error would have been found, and reported, in NewPort()
|
||||
port, _ := ParsePort(portStr)
|
||||
// assume that any error would have been found, and reported, in [NewPort]
|
||||
port, _ := parsePortNumber(p.Port())
|
||||
return port
|
||||
}
|
||||
|
||||
// Range returns the start/end port numbers of a Port range as ints
|
||||
func (p Port) Range() (int, int, error) {
|
||||
return ParsePortRangeToInt(p.Port())
|
||||
portRange := p.Port()
|
||||
if portRange == "" {
|
||||
return 0, 0, nil
|
||||
}
|
||||
return parsePortRange(portRange)
|
||||
}
|
||||
|
||||
// SplitProtoPort splits a port(range) and protocol, formatted as "<portnum>/[<proto>]"
|
||||
@@ -173,6 +173,10 @@ func splitParts(rawport string) (hostIP, hostPort, containerPort string) {
|
||||
func ParsePortSpec(rawPort string) ([]PortMapping, error) {
|
||||
ip, hostPort, containerPort := splitParts(rawPort)
|
||||
proto, containerPort := SplitProtoPort(containerPort)
|
||||
if containerPort == "" {
|
||||
return nil, fmt.Errorf("no port specified: %s<empty>", rawPort)
|
||||
}
|
||||
|
||||
proto = strings.ToLower(proto)
|
||||
if err := validateProto(proto); err != nil {
|
||||
return nil, err
|
||||
@@ -189,18 +193,15 @@ func ParsePortSpec(rawPort string) ([]PortMapping, error) {
|
||||
if ip != "" && net.ParseIP(ip) == nil {
|
||||
return nil, errors.New("invalid IP address: " + ip)
|
||||
}
|
||||
if containerPort == "" {
|
||||
return nil, fmt.Errorf("no port specified: %s<empty>", rawPort)
|
||||
}
|
||||
|
||||
startPort, endPort, err := ParsePortRange(containerPort)
|
||||
startPort, endPort, err := parsePortRange(containerPort)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid containerPort: " + containerPort)
|
||||
}
|
||||
|
||||
var startHostPort, endHostPort uint64
|
||||
var startHostPort, endHostPort int
|
||||
if hostPort != "" {
|
||||
startHostPort, endHostPort, err = ParsePortRange(hostPort)
|
||||
startHostPort, endHostPort, err = parsePortRange(hostPort)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid hostPort: " + hostPort)
|
||||
}
|
||||
@@ -217,19 +218,18 @@ func ParsePortSpec(rawPort string) ([]PortMapping, error) {
|
||||
count := endPort - startPort + 1
|
||||
ports := make([]PortMapping, 0, count)
|
||||
|
||||
for i := uint64(0); i < count; i++ {
|
||||
cPort := Port(strconv.FormatUint(startPort+i, 10) + "/" + proto)
|
||||
for i := range count {
|
||||
hPort := ""
|
||||
if hostPort != "" {
|
||||
hPort = strconv.FormatUint(startHostPort+i, 10)
|
||||
hPort = strconv.Itoa(startHostPort + i)
|
||||
// Set hostPort to a range only if there is a single container port
|
||||
// and a dynamic host port.
|
||||
if count == 1 && startHostPort != endHostPort {
|
||||
hPort += "-" + strconv.FormatUint(endHostPort, 10)
|
||||
hPort += "-" + strconv.Itoa(endHostPort)
|
||||
}
|
||||
}
|
||||
ports = append(ports, PortMapping{
|
||||
Port: cPort,
|
||||
Port: Port(strconv.Itoa(startPort+i) + "/" + proto),
|
||||
Binding: PortBinding{HostIP: ip, HostPort: hPort},
|
||||
})
|
||||
}
|
||||
|
||||
+42
-15
@@ -2,32 +2,59 @@ package nat
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ParsePortRange parses and validates the specified string as a port-range (8000-9000)
|
||||
func ParsePortRange(ports string) (uint64, uint64, error) {
|
||||
// ParsePortRange parses and validates the specified string as a port range (e.g., "8000-9000").
|
||||
func ParsePortRange(ports string) (startPort, endPort uint64, _ error) {
|
||||
start, end, err := parsePortRange(ports)
|
||||
return uint64(start), uint64(end), err
|
||||
}
|
||||
|
||||
// parsePortRange parses and validates the specified string as a port range (e.g., "8000-9000").
|
||||
func parsePortRange(ports string) (startPort, endPort int, _ error) {
|
||||
if ports == "" {
|
||||
return 0, 0, errors.New("empty string specified for ports")
|
||||
}
|
||||
if !strings.Contains(ports, "-") {
|
||||
start, err := strconv.ParseUint(ports, 10, 16)
|
||||
end := start
|
||||
return start, end, err
|
||||
start, end, ok := strings.Cut(ports, "-")
|
||||
|
||||
startPort, err := parsePortNumber(start)
|
||||
if err != nil {
|
||||
return 0, 0, fmt.Errorf("invalid start port '%s': %w", start, err)
|
||||
}
|
||||
if !ok || start == end {
|
||||
return startPort, startPort, nil
|
||||
}
|
||||
|
||||
parts := strings.Split(ports, "-")
|
||||
start, err := strconv.ParseUint(parts[0], 10, 16)
|
||||
endPort, err = parsePortNumber(end)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
return 0, 0, fmt.Errorf("invalid end port '%s': %w", end, err)
|
||||
}
|
||||
end, err := strconv.ParseUint(parts[1], 10, 16)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
if endPort < startPort {
|
||||
return 0, 0, errors.New("invalid port range: " + ports)
|
||||
}
|
||||
if end < start {
|
||||
return 0, 0, errors.New("invalid range specified for port: " + ports)
|
||||
return startPort, endPort, nil
|
||||
}
|
||||
|
||||
// parsePortNumber parses rawPort into an int, unwrapping strconv errors
|
||||
// and returning a single "out of range" error for any value outside 0–65535.
|
||||
func parsePortNumber(rawPort string) (int, error) {
|
||||
if rawPort == "" {
|
||||
return 0, errors.New("value is empty")
|
||||
}
|
||||
return start, end, nil
|
||||
port, err := strconv.ParseInt(rawPort, 10, 0)
|
||||
if err != nil {
|
||||
var numErr *strconv.NumError
|
||||
if errors.As(err, &numErr) {
|
||||
err = numErr.Err
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
if port < 0 || port > 65535 {
|
||||
return 0, errors.New("value out of range (0–65535)")
|
||||
}
|
||||
|
||||
return int(port), nil
|
||||
}
|
||||
|
||||
+27
-15
@@ -34,8 +34,10 @@ func Sort(ports []Port, predicate func(i, j Port) bool) {
|
||||
}
|
||||
|
||||
type portMapEntry struct {
|
||||
port Port
|
||||
binding PortBinding
|
||||
port Port
|
||||
binding *PortBinding
|
||||
portInt int
|
||||
portProto string
|
||||
}
|
||||
|
||||
type portMapSorter []portMapEntry
|
||||
@@ -48,23 +50,36 @@ func (s portMapSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
// 2. larger port
|
||||
// 3. port with tcp protocol
|
||||
func (s portMapSorter) Less(i, j int) bool {
|
||||
pi, pj := s[i].port, s[j].port
|
||||
hpi, hpj := toInt(s[i].binding.HostPort), toInt(s[j].binding.HostPort)
|
||||
return hpi > hpj || pi.Int() > pj.Int() || (pi.Int() == pj.Int() && strings.ToLower(pi.Proto()) == "tcp")
|
||||
pi, pj := s[i].portInt, s[j].portInt
|
||||
var hpi, hpj int
|
||||
if s[i].binding != nil {
|
||||
hpi = toInt(s[i].binding.HostPort)
|
||||
}
|
||||
if s[j].binding != nil {
|
||||
hpj = toInt(s[j].binding.HostPort)
|
||||
}
|
||||
return hpi > hpj || pi > pj || (pi == pj && strings.EqualFold(s[i].portProto, "tcp"))
|
||||
}
|
||||
|
||||
// SortPortMap sorts the list of ports and their respected mapping. The ports
|
||||
// will explicit HostPort will be placed first.
|
||||
func SortPortMap(ports []Port, bindings PortMap) {
|
||||
func SortPortMap(ports []Port, bindings map[Port][]PortBinding) {
|
||||
s := portMapSorter{}
|
||||
for _, p := range ports {
|
||||
portInt, portProto := p.Int(), p.Proto()
|
||||
if binding, ok := bindings[p]; ok && len(binding) > 0 {
|
||||
for _, b := range binding {
|
||||
s = append(s, portMapEntry{port: p, binding: b})
|
||||
s = append(s, portMapEntry{
|
||||
port: p, binding: &b,
|
||||
portInt: portInt, portProto: portProto,
|
||||
})
|
||||
}
|
||||
bindings[p] = []PortBinding{}
|
||||
} else {
|
||||
s = append(s, portMapEntry{port: p})
|
||||
s = append(s, portMapEntry{
|
||||
port: p,
|
||||
portInt: portInt, portProto: portProto,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,16 +96,13 @@ func SortPortMap(ports []Port, bindings PortMap) {
|
||||
i++
|
||||
}
|
||||
// reorder bindings for this port
|
||||
if _, ok := bindings[entry.port]; ok {
|
||||
bindings[entry.port] = append(bindings[entry.port], entry.binding)
|
||||
if entry.binding != nil {
|
||||
bindings[entry.port] = append(bindings[entry.port], *entry.binding)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func toInt(s string) uint64 {
|
||||
i, _, err := ParsePortRange(s)
|
||||
if err != nil {
|
||||
i = 0
|
||||
}
|
||||
func toInt(s string) int {
|
||||
i, _, _ := parsePortRange(s)
|
||||
return i
|
||||
}
|
||||
|
||||
+30
-30
@@ -1,48 +1,57 @@
|
||||
package sockets
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var errClosed = errors.New("use of closed network connection")
|
||||
|
||||
// InmemSocket implements net.Listener using in-memory only connections.
|
||||
type InmemSocket struct {
|
||||
chConn chan net.Conn
|
||||
chClose chan struct{}
|
||||
addr string
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// dummyAddr is used to satisfy net.Addr for the in-mem socket
|
||||
// it is just stored as a string and returns the string for all calls
|
||||
type dummyAddr string
|
||||
|
||||
// NewInmemSocket creates an in-memory only net.Listener
|
||||
// The addr argument can be any string, but is used to satisfy the `Addr()` part
|
||||
// of the net.Listener interface
|
||||
// Network returns the addr string, satisfies net.Addr
|
||||
func (a dummyAddr) Network() string {
|
||||
return string(a)
|
||||
}
|
||||
|
||||
// String returns the string form
|
||||
func (a dummyAddr) String() string {
|
||||
return string(a)
|
||||
}
|
||||
|
||||
// InmemSocket implements [net.Listener] using in-memory only connections.
|
||||
type InmemSocket struct {
|
||||
chConn chan net.Conn
|
||||
chClose chan struct{}
|
||||
addr dummyAddr
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// NewInmemSocket creates an in-memory only [net.Listener]. The addr argument
|
||||
// can be any string, but is used to satisfy the [net.Listener.Addr] part
|
||||
// of the [net.Listener] interface
|
||||
func NewInmemSocket(addr string, bufSize int) *InmemSocket {
|
||||
return &InmemSocket{
|
||||
chConn: make(chan net.Conn, bufSize),
|
||||
chClose: make(chan struct{}),
|
||||
addr: addr,
|
||||
addr: dummyAddr(addr),
|
||||
}
|
||||
}
|
||||
|
||||
// Addr returns the socket's addr string to satisfy net.Listener
|
||||
func (s *InmemSocket) Addr() net.Addr {
|
||||
return dummyAddr(s.addr)
|
||||
return s.addr
|
||||
}
|
||||
|
||||
// Accept implements the Accept method in the Listener interface; it waits for the next call and returns a generic Conn.
|
||||
// Accept implements the Accept method in the Listener interface; it waits
|
||||
// for the next call and returns a generic Conn. It returns a [net.ErrClosed]
|
||||
// if the connection is already closed.
|
||||
func (s *InmemSocket) Accept() (net.Conn, error) {
|
||||
select {
|
||||
case conn := <-s.chConn:
|
||||
return conn, nil
|
||||
case <-s.chClose:
|
||||
return nil, errClosed
|
||||
return nil, net.ErrClosed
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,24 +67,15 @@ func (s *InmemSocket) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Dial is used to establish a connection with the in-mem server
|
||||
// Dial is used to establish a connection with the in-mem server.
|
||||
// It returns a [net.ErrClosed] if the connection is already closed.
|
||||
func (s *InmemSocket) Dial(network, addr string) (net.Conn, error) {
|
||||
srvConn, clientConn := net.Pipe()
|
||||
select {
|
||||
case s.chConn <- srvConn:
|
||||
case <-s.chClose:
|
||||
return nil, errClosed
|
||||
return nil, net.ErrClosed
|
||||
}
|
||||
|
||||
return clientConn, nil
|
||||
}
|
||||
|
||||
// Network returns the addr string, satisfies net.Addr
|
||||
func (a dummyAddr) Network() string {
|
||||
return string(a)
|
||||
}
|
||||
|
||||
// String returns the string form
|
||||
func (a dummyAddr) String() string {
|
||||
return string(a)
|
||||
}
|
||||
|
||||
-31
@@ -1,31 +0,0 @@
|
||||
package sockets
|
||||
|
||||
import (
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetProxyEnv allows access to the uppercase and the lowercase forms of
|
||||
// proxy-related variables. See the Go specification for details on these
|
||||
// variables. https://golang.org/pkg/net/http/
|
||||
//
|
||||
// Deprecated: this function was used as helper for [DialerFromEnvironment] and is no longer used. It will be removed in the next release.
|
||||
func GetProxyEnv(key string) string {
|
||||
proxyValue := os.Getenv(strings.ToUpper(key))
|
||||
if proxyValue == "" {
|
||||
return os.Getenv(strings.ToLower(key))
|
||||
}
|
||||
return proxyValue
|
||||
}
|
||||
|
||||
// DialerFromEnvironment was previously used to configure a net.Dialer to route
|
||||
// connections through a SOCKS proxy.
|
||||
//
|
||||
// Deprecated: SOCKS proxies are now supported by configuring only
|
||||
// http.Transport.Proxy, and no longer require changing http.Transport.Dial.
|
||||
// Therefore, only [sockets.ConfigureTransport] needs to be called, and any
|
||||
// [sockets.DialerFromEnvironment] calls can be dropped.
|
||||
func DialerFromEnvironment(direct *net.Dialer) (*net.Dialer, error) {
|
||||
return direct, nil
|
||||
}
|
||||
+12
-12
@@ -27,11 +27,19 @@ var ErrProtocolNotAvailable = errors.New("protocol not available")
|
||||
// make sure you do it _after_ any subsequent calls to ConfigureTransport is made against the same
|
||||
// [http.Transport].
|
||||
func ConfigureTransport(tr *http.Transport, proto, addr string) error {
|
||||
if tr.MaxIdleConns == 0 {
|
||||
// prevent long-lived processes from leaking connections
|
||||
// due to idle connections not being released.
|
||||
//
|
||||
// TODO: see if we can also address this from the server side; see: https://github.com/moby/moby/issues/45539
|
||||
tr.MaxIdleConns = 6
|
||||
tr.IdleConnTimeout = 30 * time.Second
|
||||
}
|
||||
switch proto {
|
||||
case "unix":
|
||||
return configureUnixTransport(tr, proto, addr)
|
||||
return configureUnixTransport(tr, addr)
|
||||
case "npipe":
|
||||
return configureNpipeTransport(tr, proto, addr)
|
||||
return configureNpipeTransport(tr, addr)
|
||||
default:
|
||||
tr.Proxy = http.ProxyFromEnvironment
|
||||
tr.DisableCompression = false
|
||||
@@ -42,15 +50,7 @@ func ConfigureTransport(tr *http.Transport, proto, addr string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DialPipe connects to a Windows named pipe. It is not supported on
|
||||
// non-Windows platforms.
|
||||
//
|
||||
// Deprecated: use [github.com/Microsoft/go-winio.DialPipe] or [github.com/Microsoft/go-winio.DialPipeContext].
|
||||
func DialPipe(addr string, timeout time.Duration) (net.Conn, error) {
|
||||
return dialPipe(addr, timeout)
|
||||
}
|
||||
|
||||
func configureUnixTransport(tr *http.Transport, proto, addr string) error {
|
||||
func configureUnixTransport(tr *http.Transport, addr string) error {
|
||||
if len(addr) > maxUnixSocketPathSize {
|
||||
return fmt.Errorf("unix socket path %q is too long", addr)
|
||||
}
|
||||
@@ -60,7 +60,7 @@ func configureUnixTransport(tr *http.Transport, proto, addr string) error {
|
||||
Timeout: defaultTimeout,
|
||||
}
|
||||
tr.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
|
||||
return dialer.DialContext(ctx, proto, addr)
|
||||
return dialer.DialContext(ctx, "unix", addr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
+1
-12
@@ -2,17 +2,6 @@
|
||||
|
||||
package sockets
|
||||
|
||||
import (
|
||||
"net"
|
||||
"net/http"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func configureNpipeTransport(tr *http.Transport, proto, addr string) error {
|
||||
func configureNpipeTransport(any, string) error {
|
||||
return ErrProtocolNotAvailable
|
||||
}
|
||||
|
||||
func dialPipe(_ string, _ time.Duration) (net.Conn, error) {
|
||||
return nil, syscall.EAFNOSUPPORT
|
||||
}
|
||||
|
||||
+1
-6
@@ -4,12 +4,11 @@ import (
|
||||
"context"
|
||||
"net"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/Microsoft/go-winio"
|
||||
)
|
||||
|
||||
func configureNpipeTransport(tr *http.Transport, proto, addr string) error {
|
||||
func configureNpipeTransport(tr *http.Transport, addr string) error {
|
||||
// No need for compression in local communications.
|
||||
tr.DisableCompression = true
|
||||
tr.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
|
||||
@@ -17,7 +16,3 @@ func configureNpipeTransport(tr *http.Transport, proto, addr string) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func dialPipe(addr string, timeout time.Duration) (net.Conn, error) {
|
||||
return winio.DialPipe(addr, &timeout)
|
||||
}
|
||||
|
||||
+123
-1
@@ -1,6 +1,128 @@
|
||||
package sockets
|
||||
|
||||
import "net"
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/Microsoft/go-winio"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
// BasePermissions defines the default DACL, which allows Administrators
|
||||
// and LocalSystem full access (similar to defaults used in [moby]);
|
||||
//
|
||||
// - D:P: DACL without inheritance (protected, (P)).
|
||||
// - (A;;GA;;;BA): Allow full access (GA) for built-in Administrators (BA).
|
||||
// - (A;;GA;;;SY); Allow full access (GA) for LocalSystem (SY).
|
||||
// - Any other user is denied access.
|
||||
//
|
||||
// [moby]: https://github.com/moby/moby/blob/6b45c76a233b1b8b56465f76c21c09fd7920e82d/daemon/listeners/listeners_windows.go#L53-L59
|
||||
const BasePermissions = "D:P(A;;GA;;;BA)(A;;GA;;;SY)"
|
||||
|
||||
// WithBasePermissions sets a default DACL, which allows Administrators
|
||||
// and LocalSystem full access (similar to defaults used in [moby]);
|
||||
//
|
||||
// - D:P: DACL without inheritance (protected, (P)).
|
||||
// - (A;;GA;;;BA): Allow full access (GA) for built-in Administrators (BA).
|
||||
// - (A;;GA;;;SY); Allow full access (GA) for LocalSystem (SY).
|
||||
// - Any other user is denied access.
|
||||
//
|
||||
// [moby]: https://github.com/moby/moby/blob/6b45c76a233b1b8b56465f76c21c09fd7920e82d/daemon/listeners/listeners_windows.go#L53-L59
|
||||
func WithBasePermissions() SockOption {
|
||||
return withSDDL(BasePermissions)
|
||||
}
|
||||
|
||||
// WithAdditionalUsersAndGroups modifies the socket file's DACL to grant
|
||||
// access to additional users and groups.
|
||||
//
|
||||
// It sets [BasePermissions] on the socket path and grants the given additional
|
||||
// users and groups to generic read (GR) and write (GW) access. It returns
|
||||
// an error if no groups were given, when failing to resolve any of the
|
||||
// additional users and groups, or when failing to apply the ACL.
|
||||
func WithAdditionalUsersAndGroups(additionalUsersAndGroups []string) SockOption {
|
||||
return func(path string) error {
|
||||
if len(additionalUsersAndGroups) == 0 {
|
||||
return errors.New("no additional users specified")
|
||||
}
|
||||
sd, err := getSecurityDescriptor(additionalUsersAndGroups...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("looking up SID: %w", err)
|
||||
}
|
||||
return withSDDL(sd)(path)
|
||||
}
|
||||
}
|
||||
|
||||
// withSDDL applies the given SDDL to the socket. It returns an error
|
||||
// when failing parse the SDDL, or if the DACL was defaulted.
|
||||
//
|
||||
// TODO(thaJeztah); this is not exported yet, as some of the checks may need review if they're not too opinionated.
|
||||
func withSDDL(sddl string) SockOption {
|
||||
return func(path string) error {
|
||||
sd, err := windows.SecurityDescriptorFromString(sddl)
|
||||
if err != nil {
|
||||
return fmt.Errorf("parsing SDDL: %w", err)
|
||||
}
|
||||
dacl, defaulted, err := sd.DACL()
|
||||
if err != nil {
|
||||
return fmt.Errorf("extracting DACL: %w", err)
|
||||
}
|
||||
if dacl == nil || defaulted {
|
||||
// should never be hit with our [DefaultPermissions],
|
||||
// as it contains "D:" and "P" (protected, don't inherit).
|
||||
return errors.New("no DACL found in security descriptor or defaulted")
|
||||
}
|
||||
return windows.SetNamedSecurityInfo(
|
||||
path,
|
||||
windows.SE_FILE_OBJECT,
|
||||
windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION,
|
||||
nil, // do not change the owner
|
||||
nil, // do not change the owner
|
||||
dacl,
|
||||
nil,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// NewUnixSocket creates a new unix socket.
|
||||
//
|
||||
// It sets [BasePermissions] on the socket path and grants the given additional
|
||||
// users and groups to generic read (GR) and write (GW) access. It returns
|
||||
// an error when failing to resolve any of the additional users and groups,
|
||||
// or when failing to apply the ACL.
|
||||
func NewUnixSocket(path string, additionalUsersAndGroups []string) (net.Listener, error) {
|
||||
var opts []SockOption
|
||||
if len(additionalUsersAndGroups) > 0 {
|
||||
opts = append(opts, WithAdditionalUsersAndGroups(additionalUsersAndGroups))
|
||||
} else {
|
||||
opts = append(opts, WithBasePermissions())
|
||||
}
|
||||
return NewUnixSocketWithOpts(path, opts...)
|
||||
}
|
||||
|
||||
// getSecurityDescriptor returns the DACL for the Unix socket.
|
||||
//
|
||||
// By default, it grants [BasePermissions], but allows for additional
|
||||
// users and groups to get generic read (GR) and write (GW) access. It
|
||||
// returns an error when failing to resolve any of the additional users
|
||||
// and groups.
|
||||
func getSecurityDescriptor(additionalUsersAndGroups ...string) (string, error) {
|
||||
sddl := BasePermissions
|
||||
|
||||
// Grant generic read (GR) and write (GW) access to whatever
|
||||
// additional users or groups were specified.
|
||||
//
|
||||
// TODO(thaJeztah): should we fail on, or remove duplicates?
|
||||
for _, g := range additionalUsersAndGroups {
|
||||
sid, err := winio.LookupSidByName(strings.TrimSpace(g))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("looking up SID: %w", err)
|
||||
}
|
||||
sddl += fmt.Sprintf("(A;;GRGW;;;%s)", sid)
|
||||
}
|
||||
return sddl, nil
|
||||
}
|
||||
|
||||
func listenUnix(path string) (net.Listener, error) {
|
||||
return net.Listen("unix", path)
|
||||
|
||||
+7
-11
@@ -1,16 +1,12 @@
|
||||
package tlsconfig
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"runtime"
|
||||
)
|
||||
import "crypto/x509"
|
||||
|
||||
// SystemCertPool returns a copy of the system cert pool,
|
||||
// returns an error if failed to load or empty pool on windows.
|
||||
// SystemCertPool returns a copy of the system cert pool.
|
||||
//
|
||||
// Deprecated: use [x509.SystemCertPool] instead.
|
||||
//
|
||||
//go:fix inline
|
||||
func SystemCertPool() (*x509.CertPool, error) {
|
||||
certpool, err := x509.SystemCertPool()
|
||||
if err != nil && runtime.GOOS == "windows" {
|
||||
return x509.NewCertPool(), nil
|
||||
}
|
||||
return certpool, err
|
||||
return x509.SystemCertPool()
|
||||
}
|
||||
|
||||
+20
-8
@@ -34,6 +34,9 @@ type Options struct {
|
||||
// the system pool will be used.
|
||||
ExclusiveRootPools bool
|
||||
MinVersion uint16
|
||||
|
||||
// systemCertPool allows mocking the system cert-pool for testing.
|
||||
systemCertPool func() (*x509.CertPool, error)
|
||||
}
|
||||
|
||||
// DefaultServerAcceptedCiphers should be uses by code which already has a crypto/tls
|
||||
@@ -47,6 +50,8 @@ var defaultCipherSuites = []uint16{
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
|
||||
tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256,
|
||||
tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256,
|
||||
}
|
||||
|
||||
// ServerDefault returns a secure-enough TLS configuration for the server TLS configuration.
|
||||
@@ -75,26 +80,33 @@ func defaultConfig(ops ...func(*tls.Config)) *tls.Config {
|
||||
}
|
||||
|
||||
// certPool returns an X.509 certificate pool from `caFile`, the certificate file.
|
||||
func certPool(caFile string, exclusivePool bool) (*x509.CertPool, error) {
|
||||
func certPool(opts Options) (*x509.CertPool, error) {
|
||||
// If we should verify the server, we need to load a trusted ca
|
||||
var (
|
||||
pool *x509.CertPool
|
||||
err error
|
||||
)
|
||||
if exclusivePool {
|
||||
if opts.ExclusiveRootPools {
|
||||
pool = x509.NewCertPool()
|
||||
} else {
|
||||
pool, err = SystemCertPool()
|
||||
if opts.systemCertPool != nil {
|
||||
pool, err = opts.systemCertPool()
|
||||
} else {
|
||||
pool, err = x509.SystemCertPool()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read system certificates: %v", err)
|
||||
}
|
||||
}
|
||||
pemData, err := os.ReadFile(caFile)
|
||||
if opts.CAFile == "" {
|
||||
return pool, nil
|
||||
}
|
||||
pemData, err := os.ReadFile(opts.CAFile)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read CA certificate %q: %v", caFile, err)
|
||||
return nil, fmt.Errorf("could not read CA certificate %q: %v", opts.CAFile, err)
|
||||
}
|
||||
if !pool.AppendCertsFromPEM(pemData) {
|
||||
return nil, fmt.Errorf("failed to append certificates from PEM file: %q", caFile)
|
||||
return nil, fmt.Errorf("failed to append certificates from PEM file: %q", opts.CAFile)
|
||||
}
|
||||
return pool, nil
|
||||
}
|
||||
@@ -197,7 +209,7 @@ func Client(options Options) (*tls.Config, error) {
|
||||
tlsConfig := defaultConfig()
|
||||
tlsConfig.InsecureSkipVerify = options.InsecureSkipVerify
|
||||
if !options.InsecureSkipVerify && options.CAFile != "" {
|
||||
CAs, err := certPool(options.CAFile, options.ExclusiveRootPools)
|
||||
CAs, err := certPool(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -230,7 +242,7 @@ func Server(options Options) (*tls.Config, error) {
|
||||
}
|
||||
tlsConfig.Certificates = []tls.Certificate{tlsCert}
|
||||
if options.ClientAuth >= tls.VerifyClientCertIfGiven && options.CAFile != "" {
|
||||
CAs, err := certPool(options.CAFile, options.ExclusiveRootPools)
|
||||
CAs, err := certPool(options)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Vendored
+2
-2
@@ -339,8 +339,8 @@ github.com/docker/docker/pkg/namesgenerator
|
||||
## explicit; go 1.21
|
||||
github.com/docker/docker-credential-helpers/client
|
||||
github.com/docker/docker-credential-helpers/credentials
|
||||
# github.com/docker/go-connections v0.6.0
|
||||
## explicit; go 1.18
|
||||
# github.com/docker/go-connections v0.7.0
|
||||
## explicit; go 1.23
|
||||
github.com/docker/go-connections/nat
|
||||
github.com/docker/go-connections/sockets
|
||||
github.com/docker/go-connections/tlsconfig
|
||||
|
||||
Reference in New Issue
Block a user