Merge pull request #1470 from crazy-max/dependabot/go_modules/github.com/docker/go-connections-0.6.0

chore(deps): bump github.com/docker/go-connections from 0.5.0 to 0.6.0
This commit is contained in:
CrazyMax
2025-08-09 22:36:46 +02:00
committed by GitHub
15 changed files with 224 additions and 232 deletions

2
go.mod
View File

@@ -15,7 +15,7 @@ require (
github.com/crazy-max/gonfig v0.7.1 github.com/crazy-max/gonfig v0.7.1
github.com/distribution/reference v0.6.0 github.com/distribution/reference v0.6.0
github.com/docker/docker v28.3.3+incompatible github.com/docker/docker v28.3.3+incompatible
github.com/docker/go-connections v0.5.0 github.com/docker/go-connections v0.6.0
github.com/docker/go-units v0.5.0 github.com/docker/go-units v0.5.0
github.com/dromara/carbon/v2 v2.6.11 github.com/dromara/carbon/v2 v2.6.11
github.com/eclipse/paho.mqtt.golang v1.5.0 github.com/eclipse/paho.mqtt.golang v1.5.0

4
go.sum
View File

@@ -94,8 +94,8 @@ github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjY
github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= 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-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=

View File

@@ -2,6 +2,7 @@
package nat package nat
import ( import (
"errors"
"fmt" "fmt"
"net" "net"
"strconv" "strconv"
@@ -43,19 +44,19 @@ func NewPort(proto, port string) (Port, error) {
// ParsePort parses the port number string and returns an int // ParsePort parses the port number string and returns an int
func ParsePort(rawPort string) (int, error) { func ParsePort(rawPort string) (int, error) {
if len(rawPort) == 0 { if rawPort == "" {
return 0, nil return 0, nil
} }
port, err := strconv.ParseUint(rawPort, 10, 16) port, err := strconv.ParseUint(rawPort, 10, 16)
if err != nil { if err != nil {
return 0, err return 0, fmt.Errorf("invalid port '%s': %w", rawPort, errors.Unwrap(err))
} }
return int(port), nil return int(port), nil
} }
// ParsePortRangeToInt parses the port range string and returns start/end ints // ParsePortRangeToInt parses the port range string and returns start/end ints
func ParsePortRangeToInt(rawPort string) (int, int, error) { func ParsePortRangeToInt(rawPort string) (int, int, error) {
if len(rawPort) == 0 { if rawPort == "" {
return 0, 0, nil return 0, 0, nil
} }
start, end, err := ParsePortRange(rawPort) start, end, err := ParsePortRange(rawPort)
@@ -91,29 +92,31 @@ func (p Port) Range() (int, int, error) {
return ParsePortRangeToInt(p.Port()) return ParsePortRangeToInt(p.Port())
} }
// SplitProtoPort splits a port in the format of proto/port // SplitProtoPort splits a port(range) and protocol, formatted as "<portnum>/[<proto>]"
func SplitProtoPort(rawPort string) (string, string) { // "<startport-endport>/[<proto>]". It returns an empty string for both if
parts := strings.Split(rawPort, "/") // no port(range) is provided. If a port(range) is provided, but no protocol,
l := len(parts) // the default ("tcp") protocol is returned.
if len(rawPort) == 0 || l == 0 || len(parts[0]) == 0 { //
// SplitProtoPort does not validate or normalize the returned values.
func SplitProtoPort(rawPort string) (proto string, port string) {
port, proto, _ = strings.Cut(rawPort, "/")
if port == "" {
return "", "" return "", ""
} }
if l == 1 { if proto == "" {
return "tcp", rawPort proto = "tcp"
} }
if len(parts[1]) == 0 { return proto, port
return "tcp", parts[0]
}
return parts[1], parts[0]
} }
func validateProto(proto string) bool { func validateProto(proto string) error {
for _, availableProto := range []string{"tcp", "udp", "sctp"} { switch proto {
if availableProto == proto { case "tcp", "udp", "sctp":
return true // All good
return nil
default:
return errors.New("invalid proto: " + proto)
} }
}
return false
} }
// ParsePortSpecs receives port specs in the format of ip:public:private/proto and parses // ParsePortSpecs receives port specs in the format of ip:public:private/proto and parses
@@ -123,22 +126,18 @@ func ParsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding,
exposedPorts = make(map[Port]struct{}, len(ports)) exposedPorts = make(map[Port]struct{}, len(ports))
bindings = make(map[Port][]PortBinding) bindings = make(map[Port][]PortBinding)
) )
for _, rawPort := range ports { for _, p := range ports {
portMappings, err := ParsePortSpec(rawPort) portMappings, err := ParsePortSpec(p)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
for _, portMapping := range portMappings { for _, pm := range portMappings {
port := portMapping.Port port := pm.Port
if _, exists := exposedPorts[port]; !exists { if _, ok := exposedPorts[port]; !ok {
exposedPorts[port] = struct{}{} exposedPorts[port] = struct{}{}
} }
bslice, exists := bindings[port] bindings[port] = append(bindings[port], pm.Binding)
if !exists {
bslice = []PortBinding{}
}
bindings[port] = append(bslice, portMapping.Binding)
} }
} }
return exposedPorts, bindings, nil return exposedPorts, bindings, nil
@@ -150,28 +149,34 @@ type PortMapping struct {
Binding PortBinding Binding PortBinding
} }
func splitParts(rawport string) (string, string, string) { func (p *PortMapping) String() string {
parts := strings.Split(rawport, ":") return net.JoinHostPort(p.Binding.HostIP, p.Binding.HostPort+":"+string(p.Port))
n := len(parts) }
containerPort := parts[n-1]
switch n { func splitParts(rawport string) (hostIP, hostPort, containerPort string) {
parts := strings.Split(rawport, ":")
switch len(parts) {
case 1: case 1:
return "", "", containerPort return "", "", parts[0]
case 2: case 2:
return "", parts[0], containerPort return "", parts[0], parts[1]
case 3: case 3:
return parts[0], parts[1], containerPort return parts[0], parts[1], parts[2]
default: default:
return strings.Join(parts[:n-2], ":"), parts[n-2], containerPort n := len(parts)
return strings.Join(parts[:n-2], ":"), parts[n-2], parts[n-1]
} }
} }
// ParsePortSpec parses a port specification string into a slice of PortMappings // ParsePortSpec parses a port specification string into a slice of PortMappings
func ParsePortSpec(rawPort string) ([]PortMapping, error) { func ParsePortSpec(rawPort string) ([]PortMapping, error) {
var proto string
ip, hostPort, containerPort := splitParts(rawPort) ip, hostPort, containerPort := splitParts(rawPort)
proto, containerPort = SplitProtoPort(containerPort) proto, containerPort := SplitProtoPort(containerPort)
proto = strings.ToLower(proto)
if err := validateProto(proto); err != nil {
return nil, err
}
if ip != "" && ip[0] == '[' { if ip != "" && ip[0] == '[' {
// Strip [] from IPV6 addresses // Strip [] from IPV6 addresses
@@ -182,7 +187,7 @@ func ParsePortSpec(rawPort string) ([]PortMapping, error) {
ip = rawIP ip = rawIP
} }
if ip != "" && net.ParseIP(ip) == nil { if ip != "" && net.ParseIP(ip) == nil {
return nil, fmt.Errorf("invalid IP address: %s", ip) return nil, errors.New("invalid IP address: " + ip)
} }
if containerPort == "" { if containerPort == "" {
return nil, fmt.Errorf("no port specified: %s<empty>", rawPort) return nil, fmt.Errorf("no port specified: %s<empty>", rawPort)
@@ -190,18 +195,16 @@ func ParsePortSpec(rawPort string) ([]PortMapping, error) {
startPort, endPort, err := ParsePortRange(containerPort) startPort, endPort, err := ParsePortRange(containerPort)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid containerPort: %s", containerPort) return nil, errors.New("invalid containerPort: " + containerPort)
} }
var startHostPort, endHostPort uint64 = 0, 0 var startHostPort, endHostPort uint64
if len(hostPort) > 0 { if hostPort != "" {
startHostPort, endHostPort, err = ParsePortRange(hostPort) startHostPort, endHostPort, err = ParsePortRange(hostPort)
if err != nil { if err != nil {
return nil, fmt.Errorf("invalid hostPort: %s", hostPort) return nil, errors.New("invalid hostPort: " + hostPort)
} }
} if (endPort - startPort) != (endHostPort - startHostPort) {
if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) {
// Allow host port range iff containerPort is not a range. // Allow host port range iff containerPort is not a range.
// In this case, use the host port range as the dynamic // In this case, use the host port range as the dynamic
// host port range to allocate into. // host port range to allocate into.
@@ -209,32 +212,26 @@ func ParsePortSpec(rawPort string) ([]PortMapping, error) {
return nil, fmt.Errorf("invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort) return nil, fmt.Errorf("invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort)
} }
} }
if !validateProto(strings.ToLower(proto)) {
return nil, fmt.Errorf("invalid proto: %s", proto)
} }
ports := []PortMapping{} count := endPort - startPort + 1
for i := uint64(0); i <= (endPort - startPort); i++ { ports := make([]PortMapping, 0, count)
containerPort = strconv.FormatUint(startPort+i, 10)
if len(hostPort) > 0 { for i := uint64(0); i < count; i++ {
hostPort = strconv.FormatUint(startHostPort+i, 10) cPort := Port(strconv.FormatUint(startPort+i, 10) + "/" + proto)
} hPort := ""
if hostPort != "" {
hPort = strconv.FormatUint(startHostPort+i, 10)
// Set hostPort to a range only if there is a single container port // Set hostPort to a range only if there is a single container port
// and a dynamic host port. // and a dynamic host port.
if startPort == endPort && startHostPort != endHostPort { if count == 1 && startHostPort != endHostPort {
hostPort = fmt.Sprintf("%s-%s", hostPort, strconv.FormatUint(endHostPort, 10)) hPort += "-" + strconv.FormatUint(endHostPort, 10)
} }
port, err := NewPort(strings.ToLower(proto), containerPort)
if err != nil {
return nil, err
} }
ports = append(ports, PortMapping{
binding := PortBinding{ Port: cPort,
HostIP: ip, Binding: PortBinding{HostIP: ip, HostPort: hPort},
HostPort: hostPort, })
}
ports = append(ports, PortMapping{Port: port, Binding: binding})
} }
return ports, nil return ports, nil
} }

View File

@@ -1,7 +1,7 @@
package nat package nat
import ( import (
"fmt" "errors"
"strconv" "strconv"
"strings" "strings"
) )
@@ -9,7 +9,7 @@ import (
// ParsePortRange parses and validates the specified string as a port-range (8000-9000) // ParsePortRange parses and validates the specified string as a port-range (8000-9000)
func ParsePortRange(ports string) (uint64, uint64, error) { func ParsePortRange(ports string) (uint64, uint64, error) {
if ports == "" { if ports == "" {
return 0, 0, fmt.Errorf("empty string specified for ports") return 0, 0, errors.New("empty string specified for ports")
} }
if !strings.Contains(ports, "-") { if !strings.Contains(ports, "-") {
start, err := strconv.ParseUint(ports, 10, 16) start, err := strconv.ParseUint(ports, 10, 16)
@@ -27,7 +27,7 @@ func ParsePortRange(ports string) (uint64, uint64, error) {
return 0, 0, err return 0, 0, err
} }
if end < start { if end < start {
return 0, 0, fmt.Errorf("invalid range specified for port: %s", ports) return 0, 0, errors.New("invalid range specified for port: " + ports)
} }
return start, end, nil return start, end, nil
} }

View File

@@ -9,6 +9,8 @@ import (
// GetProxyEnv allows access to the uppercase and the lowercase forms of // GetProxyEnv allows access to the uppercase and the lowercase forms of
// proxy-related variables. See the Go specification for details on these // proxy-related variables. See the Go specification for details on these
// variables. https://golang.org/pkg/net/http/ // 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 { func GetProxyEnv(key string) string {
proxyValue := os.Getenv(strings.ToUpper(key)) proxyValue := os.Getenv(strings.ToUpper(key))
if proxyValue == "" { if proxyValue == "" {
@@ -19,10 +21,11 @@ func GetProxyEnv(key string) string {
// DialerFromEnvironment was previously used to configure a net.Dialer to route // DialerFromEnvironment was previously used to configure a net.Dialer to route
// connections through a SOCKS proxy. // connections through a SOCKS proxy.
// DEPRECATED: SOCKS proxies are now supported by configuring only //
// Deprecated: SOCKS proxies are now supported by configuring only
// http.Transport.Proxy, and no longer require changing http.Transport.Dial. // http.Transport.Proxy, and no longer require changing http.Transport.Dial.
// Therefore, only sockets.ConfigureTransport() needs to be called, and any // Therefore, only [sockets.ConfigureTransport] needs to be called, and any
// sockets.DialerFromEnvironment() calls can be dropped. // [sockets.DialerFromEnvironment] calls can be dropped.
func DialerFromEnvironment(direct *net.Dialer) (*net.Dialer, error) { func DialerFromEnvironment(direct *net.Dialer) (*net.Dialer, error) {
return direct, nil return direct, nil
} }

View File

@@ -2,13 +2,19 @@
package sockets package sockets
import ( import (
"context"
"errors" "errors"
"fmt"
"net" "net"
"net/http" "net/http"
"syscall"
"time" "time"
) )
const defaultTimeout = 10 * time.Second const (
defaultTimeout = 10 * time.Second
maxUnixSocketPathSize = len(syscall.RawSockaddrUnix{}.Path)
)
// ErrProtocolNotAvailable is returned when a given transport protocol is not provided by the operating system. // ErrProtocolNotAvailable is returned when a given transport protocol is not provided by the operating system.
var ErrProtocolNotAvailable = errors.New("protocol not available") var ErrProtocolNotAvailable = errors.New("protocol not available")
@@ -35,3 +41,26 @@ func ConfigureTransport(tr *http.Transport, proto, addr string) error {
} }
return nil 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 {
if len(addr) > maxUnixSocketPathSize {
return fmt.Errorf("unix socket path %q is too long", addr)
}
// No need for compression in local communications.
tr.DisableCompression = true
dialer := &net.Dialer{
Timeout: defaultTimeout,
}
tr.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
return dialer.DialContext(ctx, proto, addr)
}
return nil
}

View File

@@ -3,37 +3,16 @@
package sockets package sockets
import ( import (
"context"
"fmt"
"net" "net"
"net/http" "net/http"
"syscall" "syscall"
"time" "time"
) )
const maxUnixSocketPathSize = len(syscall.RawSockaddrUnix{}.Path)
func configureUnixTransport(tr *http.Transport, proto, addr string) error {
if len(addr) > maxUnixSocketPathSize {
return fmt.Errorf("unix socket path %q is too long", addr)
}
// No need for compression in local communications.
tr.DisableCompression = true
dialer := &net.Dialer{
Timeout: defaultTimeout,
}
tr.DialContext = func(ctx context.Context, _, _ string) (net.Conn, error) {
return dialer.DialContext(ctx, proto, addr)
}
return nil
}
func configureNpipeTransport(tr *http.Transport, proto, addr string) error { func configureNpipeTransport(tr *http.Transport, proto, addr string) error {
return ErrProtocolNotAvailable return ErrProtocolNotAvailable
} }
// DialPipe connects to a Windows named pipe. func dialPipe(_ string, _ time.Duration) (net.Conn, error) {
// This is not supported on other OSes.
func DialPipe(_ string, _ time.Duration) (net.Conn, error) {
return nil, syscall.EAFNOSUPPORT return nil, syscall.EAFNOSUPPORT
} }

View File

@@ -9,10 +9,6 @@ import (
"github.com/Microsoft/go-winio" "github.com/Microsoft/go-winio"
) )
func configureUnixTransport(tr *http.Transport, proto, addr string) error {
return ErrProtocolNotAvailable
}
func configureNpipeTransport(tr *http.Transport, proto, addr string) error { func configureNpipeTransport(tr *http.Transport, proto, addr string) error {
// No need for compression in local communications. // No need for compression in local communications.
tr.DisableCompression = true tr.DisableCompression = true
@@ -22,7 +18,6 @@ func configureNpipeTransport(tr *http.Transport, proto, addr string) error {
return nil return nil
} }
// DialPipe connects to a Windows named pipe. func dialPipe(addr string, timeout time.Duration) (net.Conn, error) {
func DialPipe(addr string, timeout time.Duration) (net.Conn, error) {
return winio.DialPipe(addr, &timeout) return winio.DialPipe(addr, &timeout)
} }

View File

@@ -1,5 +1,3 @@
//go:build !windows
/* /*
Package sockets is a simple unix domain socket wrapper. Package sockets is a simple unix domain socket wrapper.
@@ -57,26 +55,6 @@ import (
// SockOption sets up socket file's creating option // SockOption sets up socket file's creating option
type SockOption func(string) error type SockOption func(string) error
// WithChown modifies the socket file's uid and gid
func WithChown(uid, gid int) SockOption {
return func(path string) error {
if err := os.Chown(path, uid, gid); err != nil {
return err
}
return nil
}
}
// WithChmod modifies socket file's access mode.
func WithChmod(mask os.FileMode) SockOption {
return func(path string) error {
if err := os.Chmod(path, mask); err != nil {
return err
}
return nil
}
}
// NewUnixSocketWithOpts creates a unix socket with the specified options. // NewUnixSocketWithOpts creates a unix socket with the specified options.
// By default, socket permissions are 0000 (i.e.: no access for anyone); pass // By default, socket permissions are 0000 (i.e.: no access for anyone); pass
// WithChmod() and WithChown() to set the desired ownership and permissions. // WithChmod() and WithChown() to set the desired ownership and permissions.
@@ -90,22 +68,7 @@ func NewUnixSocketWithOpts(path string, opts ...SockOption) (net.Listener, error
return nil, err return nil, err
} }
// net.Listen does not allow for permissions to be set. As a result, when l, err := listenUnix(path)
// specifying custom permissions ("WithChmod()"), there is a short time
// between creating the socket and applying the permissions, during which
// the socket permissions are Less restrictive than desired.
//
// To work around this limitation of net.Listen(), we temporarily set the
// umask to 0777, which forces the socket to be created with 000 permissions
// (i.e.: no access for anyone). After that, WithChmod() must be used to set
// the desired permissions.
//
// We don't use "defer" here, to reset the umask to its original value as soon
// as possible. Ideally we'd be able to detect if WithChmod() was passed as
// an option, and skip changing umask if default permissions are used.
origUmask := syscall.Umask(0o777)
l, err := net.Listen("unix", path)
syscall.Umask(origUmask)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -119,8 +82,3 @@ func NewUnixSocketWithOpts(path string, opts ...SockOption) (net.Listener, error
return l, nil return l, nil
} }
// NewUnixSocket creates a unix socket with the specified path and group.
func NewUnixSocket(path string, gid int) (net.Listener, error) {
return NewUnixSocketWithOpts(path, WithChown(0, gid), WithChmod(0o660))
}

View File

@@ -0,0 +1,54 @@
//go:build !windows
package sockets
import (
"net"
"os"
"syscall"
)
// WithChown modifies the socket file's uid and gid
func WithChown(uid, gid int) SockOption {
return func(path string) error {
if err := os.Chown(path, uid, gid); err != nil {
return err
}
return nil
}
}
// WithChmod modifies socket file's access mode.
func WithChmod(mask os.FileMode) SockOption {
return func(path string) error {
if err := os.Chmod(path, mask); err != nil {
return err
}
return nil
}
}
// NewUnixSocket creates a unix socket with the specified path and group.
func NewUnixSocket(path string, gid int) (net.Listener, error) {
return NewUnixSocketWithOpts(path, WithChown(0, gid), WithChmod(0o660))
}
func listenUnix(path string) (net.Listener, error) {
// net.Listen does not allow for permissions to be set. As a result, when
// specifying custom permissions ("WithChmod()"), there is a short time
// between creating the socket and applying the permissions, during which
// the socket permissions are Less restrictive than desired.
//
// To work around this limitation of net.Listen(), we temporarily set the
// umask to 0777, which forces the socket to be created with 000 permissions
// (i.e.: no access for anyone). After that, WithChmod() must be used to set
// the desired permissions.
//
// We don't use "defer" here, to reset the umask to its original value as soon
// as possible. Ideally we'd be able to detect if WithChmod() was passed as
// an option, and skip changing umask if default permissions are used.
origUmask := syscall.Umask(0o777)
l, err := net.Listen("unix", path)
syscall.Umask(origUmask)
return l, err
}

View File

@@ -0,0 +1,7 @@
package sockets
import "net"
func listenUnix(path string) (net.Listener, error) {
return net.Listen("unix", path)
}

View File

@@ -34,51 +34,37 @@ type Options struct {
// the system pool will be used. // the system pool will be used.
ExclusiveRootPools bool ExclusiveRootPools bool
MinVersion uint16 MinVersion uint16
// If Passphrase is set, it will be used to decrypt a TLS private key
// if the key is encrypted.
//
// Deprecated: Use of encrypted TLS private keys has been deprecated, and
// will be removed in a future release. Golang has deprecated support for
// legacy PEM encryption (as specified in RFC 1423), as it is insecure by
// design (see https://go-review.googlesource.com/c/go/+/264159).
Passphrase string
}
// Extra (server-side) accepted CBC cipher suites - will phase out in the future
var acceptedCBCCiphers = []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA,
} }
// DefaultServerAcceptedCiphers should be uses by code which already has a crypto/tls // DefaultServerAcceptedCiphers should be uses by code which already has a crypto/tls
// options struct but wants to use a commonly accepted set of TLS cipher suites, with // options struct but wants to use a commonly accepted set of TLS cipher suites, with
// known weak algorithms removed. // known weak algorithms removed.
var DefaultServerAcceptedCiphers = append(clientCipherSuites, acceptedCBCCiphers...) var DefaultServerAcceptedCiphers = defaultCipherSuites
// defaultCipherSuites is shared by both client and server as the default set.
var defaultCipherSuites = []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
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,
}
// ServerDefault returns a secure-enough TLS configuration for the server TLS configuration. // ServerDefault returns a secure-enough TLS configuration for the server TLS configuration.
func ServerDefault(ops ...func(*tls.Config)) *tls.Config { func ServerDefault(ops ...func(*tls.Config)) *tls.Config {
tlsConfig := &tls.Config{ return defaultConfig(ops...)
// Avoid fallback by default to SSL protocols < TLS1.2
MinVersion: tls.VersionTLS12,
PreferServerCipherSuites: true,
CipherSuites: DefaultServerAcceptedCiphers,
}
for _, op := range ops {
op(tlsConfig)
}
return tlsConfig
} }
// ClientDefault returns a secure-enough TLS configuration for the client TLS configuration. // ClientDefault returns a secure-enough TLS configuration for the client TLS configuration.
func ClientDefault(ops ...func(*tls.Config)) *tls.Config { func ClientDefault(ops ...func(*tls.Config)) *tls.Config {
return defaultConfig(ops...)
}
// defaultConfig is the default config used by both client and server TLS configuration.
func defaultConfig(ops ...func(*tls.Config)) *tls.Config {
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
// Prefer TLS1.2 as the client minimum // Avoid fallback by default to SSL protocols < TLS1.2
MinVersion: tls.VersionTLS12, MinVersion: tls.VersionTLS12,
CipherSuites: clientCipherSuites, CipherSuites: defaultCipherSuites,
} }
for _, op := range ops { for _, op := range ops {
@@ -92,13 +78,13 @@ func ClientDefault(ops ...func(*tls.Config)) *tls.Config {
func certPool(caFile string, exclusivePool bool) (*x509.CertPool, error) { func certPool(caFile string, exclusivePool bool) (*x509.CertPool, error) {
// If we should verify the server, we need to load a trusted ca // If we should verify the server, we need to load a trusted ca
var ( var (
certPool *x509.CertPool pool *x509.CertPool
err error err error
) )
if exclusivePool { if exclusivePool {
certPool = x509.NewCertPool() pool = x509.NewCertPool()
} else { } else {
certPool, err = SystemCertPool() pool, err = SystemCertPool()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read system certificates: %v", err) return nil, fmt.Errorf("failed to read system certificates: %v", err)
} }
@@ -107,10 +93,10 @@ func certPool(caFile string, exclusivePool bool) (*x509.CertPool, error) {
if err != nil { 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", caFile, err)
} }
if !certPool.AppendCertsFromPEM(pemData) { 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", caFile)
} }
return certPool, nil return pool, nil
} }
// allTLSVersions lists all the TLS versions and is used by the code that validates // allTLSVersions lists all the TLS versions and is used by the code that validates
@@ -144,34 +130,32 @@ func adjustMinVersion(options Options, config *tls.Config) error {
return nil return nil
} }
// IsErrEncryptedKey returns true if the 'err' is an error of incorrect // errEncryptedKeyDeprecated is produced when we encounter an encrypted
// password when trying to decrypt a TLS private key. // (password-protected) key. From https://go-review.googlesource.com/c/go/+/264159;
// //
// Deprecated: Use of encrypted TLS private keys has been deprecated, and // > Legacy PEM encryption as specified in RFC 1423 is insecure by design. Since
// will be removed in a future release. Golang has deprecated support for // > it does not authenticate the ciphertext, it is vulnerable to padding oracle
// legacy PEM encryption (as specified in RFC 1423), as it is insecure by // > attacks that can let an attacker recover the plaintext
// design (see https://go-review.googlesource.com/c/go/+/264159). // >
func IsErrEncryptedKey(err error) bool { // > It's unfortunate that we don't implement PKCS#8 encryption so we can't
return errors.Is(err, x509.IncorrectPasswordError) // > recommend an alternative but PEM encryption is so broken that it's worth
} // > deprecating outright.
//
// Also see https://docs.docker.com/go/deprecated/
var errEncryptedKeyDeprecated = errors.New("private key is encrypted; encrypted private keys are obsolete, and not supported")
// getPrivateKey returns the private key in 'keyBytes', in PEM-encoded format. // getPrivateKey returns the private key in 'keyBytes', in PEM-encoded format.
// If the private key is encrypted, 'passphrase' is used to decrypted the // It returns an error if the file could not be decoded or was protected by
// private key. // a passphrase.
func getPrivateKey(keyBytes []byte, passphrase string) ([]byte, error) { func getPrivateKey(keyBytes []byte) ([]byte, error) {
// this section makes some small changes to code from notary/tuf/utils/x509.go // this section makes some small changes to code from notary/tuf/utils/x509.go
pemBlock, _ := pem.Decode(keyBytes) pemBlock, _ := pem.Decode(keyBytes)
if pemBlock == nil { if pemBlock == nil {
return nil, fmt.Errorf("no valid private key found") return nil, fmt.Errorf("no valid private key found")
} }
var err error
if x509.IsEncryptedPEMBlock(pemBlock) { //nolint:staticcheck // Ignore SA1019 (IsEncryptedPEMBlock is deprecated) if x509.IsEncryptedPEMBlock(pemBlock) { //nolint:staticcheck // Ignore SA1019 (IsEncryptedPEMBlock is deprecated)
keyBytes, err = x509.DecryptPEMBlock(pemBlock, []byte(passphrase)) //nolint:staticcheck // Ignore SA1019 (DecryptPEMBlock is deprecated) return nil, errEncryptedKeyDeprecated
if err != nil {
return nil, fmt.Errorf("private key is encrypted, but could not decrypt it: %w", err)
}
keyBytes = pem.EncodeToMemory(&pem.Block{Type: pemBlock.Type, Bytes: keyBytes})
} }
return keyBytes, nil return keyBytes, nil
@@ -195,7 +179,7 @@ func getCert(options Options) ([]tls.Certificate, error) {
return nil, err return nil, err
} }
prKeyBytes, err = getPrivateKey(prKeyBytes, options.Passphrase) prKeyBytes, err = getPrivateKey(prKeyBytes)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -210,7 +194,7 @@ func getCert(options Options) ([]tls.Certificate, error) {
// Client returns a TLS configuration meant to be used by a client. // Client returns a TLS configuration meant to be used by a client.
func Client(options Options) (*tls.Config, error) { func Client(options Options) (*tls.Config, error) {
tlsConfig := ClientDefault() tlsConfig := defaultConfig()
tlsConfig.InsecureSkipVerify = options.InsecureSkipVerify tlsConfig.InsecureSkipVerify = options.InsecureSkipVerify
if !options.InsecureSkipVerify && options.CAFile != "" { if !options.InsecureSkipVerify && options.CAFile != "" {
CAs, err := certPool(options.CAFile, options.ExclusiveRootPools) CAs, err := certPool(options.CAFile, options.ExclusiveRootPools)
@@ -235,7 +219,7 @@ func Client(options Options) (*tls.Config, error) {
// Server returns a TLS configuration meant to be used by a server. // Server returns a TLS configuration meant to be used by a server.
func Server(options Options) (*tls.Config, error) { func Server(options Options) (*tls.Config, error) {
tlsConfig := ServerDefault() tlsConfig := defaultConfig()
tlsConfig.ClientAuth = options.ClientAuth tlsConfig.ClientAuth = options.ClientAuth
tlsCert, err := tls.LoadX509KeyPair(options.CertFile, options.KeyFile) tlsCert, err := tls.LoadX509KeyPair(options.CertFile, options.KeyFile)
if err != nil { if err != nil {

View File

@@ -1,14 +0,0 @@
// Package tlsconfig provides primitives to retrieve secure-enough TLS configurations for both clients and servers.
package tlsconfig
import (
"crypto/tls"
)
// Client TLS cipher suites (dropping CBC ciphers for client preferred suite set)
var clientCipherSuites = []uint16{
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
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,
}

2
vendor/modules.txt vendored
View File

@@ -176,7 +176,7 @@ github.com/docker/docker/internal/multierror
## explicit; go 1.21 ## explicit; go 1.21
github.com/docker/docker-credential-helpers/client github.com/docker/docker-credential-helpers/client
github.com/docker/docker-credential-helpers/credentials github.com/docker/docker-credential-helpers/credentials
# github.com/docker/go-connections v0.5.0 # github.com/docker/go-connections v0.6.0
## explicit; go 1.18 ## explicit; go 1.18
github.com/docker/go-connections/nat github.com/docker/go-connections/nat
github.com/docker/go-connections/sockets github.com/docker/go-connections/sockets