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:
Sebastiaan van Stijn
2026-04-21 12:16:59 +02:00
parent 8c1c3e4d7f
commit 478d9b19a2
14 changed files with 304 additions and 182 deletions
+1 -1
View File
@@ -113,7 +113,7 @@ require (
github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect
github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect
github.com/docker/docker-credential-helpers v0.9.5 // indirect
github.com/docker/go-connections v0.6.0 // indirect
github.com/docker/go-connections v0.7.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fvbommel/sortorder v1.1.0 // indirect
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
+2 -2
View File
@@ -189,8 +189,8 @@ github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaft
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY=
github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c=
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-connections v0.7.0 h1:6SsRfJddP22WMrCkj19x9WKjEDTB+ahsdiGYf0mN39c=
github.com/docker/go-connections v0.7.0/go.mod h1:no1qkHdjq7kLMGUXYAduOhYPSJxxvgWBh7ogVvptn3Q=
github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
+36 -36
View File
@@ -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
View File
@@ -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 065535.
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 (065535)")
}
return int(port), nil
}
+26 -14
View File
@@ -35,7 +35,9 @@ func Sort(ports []Port, predicate func(i, j Port) bool) {
type portMapEntry struct {
port Port
binding PortBinding
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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
}
+2 -2
View File
@@ -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