cmd: per command tls flags

Signed-off-by: Antonio Murdaca <runcom@redhat.com>
This commit is contained in:
Antonio Murdaca
2016-12-03 16:27:28 +01:00
parent 93cde78d9b
commit 1215f5fe69
20 changed files with 352 additions and 159 deletions

View File

@@ -85,14 +85,10 @@ func (p Port) Port() string {
// Int returns the port number of a Port as an int
func (p Port) Int() int {
portStr := p.Port()
if len(portStr) == 0 {
return 0
}
// 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, _ := strconv.ParseUint(portStr, 10, 16)
return int(port)
port, _ := ParsePort(portStr)
return port
}
// Range returns the start/end port numbers of a Port range as ints
@@ -132,92 +128,115 @@ func ParsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding,
exposedPorts = make(map[Port]struct{}, len(ports))
bindings = make(map[Port][]PortBinding)
)
for _, rawPort := range ports {
proto := "tcp"
if i := strings.LastIndex(rawPort, "/"); i != -1 {
proto = rawPort[i+1:]
rawPort = rawPort[:i]
}
if !strings.Contains(rawPort, ":") {
rawPort = fmt.Sprintf("::%s", rawPort)
} else if len(strings.Split(rawPort, ":")) == 2 {
rawPort = fmt.Sprintf(":%s", rawPort)
}
parts, err := PartParser(portSpecTemplate, rawPort)
portMappings, err := ParsePortSpec(rawPort)
if err != nil {
return nil, nil, err
}
var (
containerPort = parts["containerPort"]
rawIP = parts["ip"]
hostPort = parts["hostPort"]
)
if rawIP != "" && net.ParseIP(rawIP) == nil {
return nil, nil, fmt.Errorf("Invalid ip address: %s", rawIP)
}
if containerPort == "" {
return nil, nil, fmt.Errorf("No port specified: %s<empty>", rawPort)
}
startPort, endPort, err := ParsePortRange(containerPort)
if err != nil {
return nil, nil, fmt.Errorf("Invalid containerPort: %s", containerPort)
}
var startHostPort, endHostPort uint64 = 0, 0
if len(hostPort) > 0 {
startHostPort, endHostPort, err = ParsePortRange(hostPort)
if err != nil {
return nil, nil, fmt.Errorf("Invalid hostPort: %s", hostPort)
}
}
if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) {
// Allow host port range iff containerPort is not a range.
// In this case, use the host port range as the dynamic
// host port range to allocate into.
if endPort != startPort {
return nil, nil, fmt.Errorf("Invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort)
}
}
if !validateProto(strings.ToLower(proto)) {
return nil, nil, fmt.Errorf("Invalid proto: %s", proto)
}
for i := uint64(0); i <= (endPort - startPort); i++ {
containerPort = strconv.FormatUint(startPort+i, 10)
if len(hostPort) > 0 {
hostPort = strconv.FormatUint(startHostPort+i, 10)
}
// Set hostPort to a range only if there is a single container port
// and a dynamic host port.
if startPort == endPort && startHostPort != endHostPort {
hostPort = fmt.Sprintf("%s-%s", hostPort, strconv.FormatUint(endHostPort, 10))
}
port, err := NewPort(strings.ToLower(proto), containerPort)
if err != nil {
return nil, nil, err
}
for _, portMapping := range portMappings {
port := portMapping.Port
if _, exists := exposedPorts[port]; !exists {
exposedPorts[port] = struct{}{}
}
binding := PortBinding{
HostIP: rawIP,
HostPort: hostPort,
}
bslice, exists := bindings[port]
if !exists {
bslice = []PortBinding{}
}
bindings[port] = append(bslice, binding)
bindings[port] = append(bslice, portMapping.Binding)
}
}
return exposedPorts, bindings, nil
}
// PortMapping is a data object mapping a Port to a PortBinding
type PortMapping struct {
Port Port
Binding PortBinding
}
func splitParts(rawport string) (string, string, string) {
parts := strings.Split(rawport, ":")
n := len(parts)
containerport := parts[n-1]
switch n {
case 1:
return "", "", containerport
case 2:
return "", parts[0], containerport
case 3:
return parts[0], parts[1], containerport
default:
return strings.Join(parts[:n-2], ":"), parts[n-2], containerport
}
}
// ParsePortSpec parses a port specification string into a slice of PortMappings
func ParsePortSpec(rawPort string) ([]PortMapping, error) {
var proto string
rawIP, hostPort, containerPort := splitParts(rawPort)
proto, containerPort = SplitProtoPort(containerPort)
// Strip [] from IPV6 addresses
ip, _, err := net.SplitHostPort(rawIP + ":")
if err != nil {
return nil, fmt.Errorf("Invalid ip address %v: %s", rawIP, err)
}
if ip != "" && net.ParseIP(ip) == nil {
return nil, fmt.Errorf("Invalid ip address: %s", ip)
}
if containerPort == "" {
return nil, fmt.Errorf("No port specified: %s<empty>", rawPort)
}
startPort, endPort, err := ParsePortRange(containerPort)
if err != nil {
return nil, fmt.Errorf("Invalid containerPort: %s", containerPort)
}
var startHostPort, endHostPort uint64 = 0, 0
if len(hostPort) > 0 {
startHostPort, endHostPort, err = ParsePortRange(hostPort)
if err != nil {
return nil, fmt.Errorf("Invalid hostPort: %s", hostPort)
}
}
if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) {
// Allow host port range iff containerPort is not a range.
// In this case, use the host port range as the dynamic
// host port range to allocate into.
if endPort != startPort {
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{}
for i := uint64(0); i <= (endPort - startPort); i++ {
containerPort = strconv.FormatUint(startPort+i, 10)
if len(hostPort) > 0 {
hostPort = strconv.FormatUint(startHostPort+i, 10)
}
// Set hostPort to a range only if there is a single container port
// and a dynamic host port.
if startPort == endPort && startHostPort != endHostPort {
hostPort = fmt.Sprintf("%s-%s", hostPort, strconv.FormatUint(endHostPort, 10))
}
port, err := NewPort(strings.ToLower(proto), containerPort)
if err != nil {
return nil, err
}
binding := PortBinding{
HostIP: ip,
HostPort: hostPort,
}
ports = append(ports, PortMapping{Port: port, Binding: binding})
}
return ports, nil
}

View File

@@ -8,6 +8,7 @@ import (
// PartParser parses and validates the specified string (data) using the specified template
// e.g. ip:public:private -> 192.168.0.1:80:8000
// DEPRECATED: do not use, this function may be removed in a future version
func PartParser(template, data string) (map[string]string, error) {
// ip:public:private
var (

View File

@@ -79,11 +79,3 @@ func (a dummyAddr) Network() string {
func (a dummyAddr) String() string {
return string(a)
}
// timeoutError is used when there is a timeout with a connection
// this implements the net.Error interface
type timeoutError struct{}
func (e *timeoutError) Error() string { return "i/o timeout" }
func (e *timeoutError) Timeout() bool { return true }
func (e *timeoutError) Temporary() bool { return true }

View File

@@ -2,6 +2,7 @@
package sockets
import (
"errors"
"net"
"net/http"
"time"
@@ -10,6 +11,9 @@ import (
// Why 32? See https://github.com/docker/docker/pull/8035.
const defaultTimeout = 32 * time.Second
// ErrProtocolNotAvailable is returned when a given transport protocol is not provided by the operating system.
var ErrProtocolNotAvailable = errors.New("protocol not available")
// ConfigureTransport configures the specified Transport according to the
// specified proto and addr.
// If the proto is unix (using a unix socket to communicate) or npipe the
@@ -17,17 +21,9 @@ const defaultTimeout = 32 * time.Second
func ConfigureTransport(tr *http.Transport, proto, addr string) error {
switch proto {
case "unix":
// No need for compression in local communications.
tr.DisableCompression = true
tr.Dial = func(_, _ string) (net.Conn, error) {
return net.DialTimeout(proto, addr, defaultTimeout)
}
return configureUnixTransport(tr, proto, addr)
case "npipe":
// No need for compression in local communications.
tr.DisableCompression = true
tr.Dial = func(_, _ string) (net.Conn, error) {
return DialPipe(addr, defaultTimeout)
}
return configureNpipeTransport(tr, proto, addr)
default:
tr.Proxy = http.ProxyFromEnvironment
dialer, err := DialerFromEnvironment(&net.Dialer{

View File

@@ -3,11 +3,31 @@
package sockets
import (
"fmt"
"net"
"net/http"
"syscall"
"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
tr.Dial = func(_, _ string) (net.Conn, error) {
return net.DialTimeout(proto, addr, defaultTimeout)
}
return nil
}
func configureNpipeTransport(tr *http.Transport, proto, addr string) error {
return ErrProtocolNotAvailable
}
// DialPipe connects to a Windows named pipe.
// This is not supported on other OSes.
func DialPipe(_ string, _ time.Duration) (net.Conn, error) {

View File

@@ -2,11 +2,25 @@ package sockets
import (
"net"
"net/http"
"time"
"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 {
// No need for compression in local communications.
tr.DisableCompression = true
tr.Dial = func(_, _ string) (net.Conn, error) {
return DialPipe(addr, defaultTimeout)
}
return nil
}
// DialPipe connects to a Windows named pipe.
func DialPipe(addr string, timeout time.Duration) (net.Conn, error) {
return winio.DialPipe(addr, &timeout)

View File

@@ -7,7 +7,7 @@ import (
)
// NewTCPSocket creates a TCP socket listener with the specified address and
// and the specified tls configuration. If TLSConfig is set, will encapsulate the
// the specified tls configuration. If TLSConfig is set, will encapsulate the
// TCP listener inside a TLS one.
func NewTCPSocket(addr string, tlsConfig *tls.Config) (net.Listener, error) {
l, err := net.Listen("tcp", addr)

View File

@@ -0,0 +1,21 @@
// +build go1.7
package tlsconfig
import (
"crypto/x509"
"runtime"
"github.com/Sirupsen/logrus"
)
// SystemCertPool returns a copy of the system cert pool,
// returns an error if failed to load or empty pool on windows.
func SystemCertPool() (*x509.CertPool, error) {
certpool, err := x509.SystemCertPool()
if err != nil && runtime.GOOS == "windows" {
logrus.Warnf("Unable to use system certificate pool: %v", err)
return x509.NewCertPool(), nil
}
return certpool, err
}

View File

@@ -0,0 +1,16 @@
// +build !go1.7
package tlsconfig
import (
"crypto/x509"
"github.com/Sirupsen/logrus"
)
// SystemCertPool returns an new empty cert pool,
// accessing system cert pool is supported in go 1.7
func SystemCertPool() (*x509.CertPool, error) {
logrus.Warn("Unable to use system certificate pool: requires building with go 1.7 or later")
return x509.NewCertPool(), nil
}

View File

@@ -46,44 +46,46 @@ var acceptedCBCCiphers = []uint16{
// known weak algorithms removed.
var DefaultServerAcceptedCiphers = append(clientCipherSuites, acceptedCBCCiphers...)
// ServerDefault is a secure-enough TLS configuration for the server TLS configuration.
var ServerDefault = tls.Config{
// Avoid fallback to SSL protocols < TLS1.0
MinVersion: tls.VersionTLS10,
PreferServerCipherSuites: true,
CipherSuites: DefaultServerAcceptedCiphers,
// ServerDefault returns a secure-enough TLS configuration for the server TLS configuration.
func ServerDefault() *tls.Config {
return &tls.Config{
// Avoid fallback to SSL protocols < TLS1.0
MinVersion: tls.VersionTLS10,
PreferServerCipherSuites: true,
CipherSuites: DefaultServerAcceptedCiphers,
}
}
// ClientDefault is a secure-enough TLS configuration for the client TLS configuration.
var ClientDefault = tls.Config{
// Prefer TLS1.2 as the client minimum
MinVersion: tls.VersionTLS12,
CipherSuites: clientCipherSuites,
// ClientDefault returns a secure-enough TLS configuration for the client TLS configuration.
func ClientDefault() *tls.Config {
return &tls.Config{
// Prefer TLS1.2 as the client minimum
MinVersion: tls.VersionTLS12,
CipherSuites: clientCipherSuites,
}
}
// certPool returns an X.509 certificate pool from `caFile`, the certificate file.
func certPool(caFile string) (*x509.CertPool, error) {
// If we should verify the server, we need to load a trusted ca
certPool := x509.NewCertPool()
certPool, err := SystemCertPool()
if err != nil {
return nil, fmt.Errorf("failed to read system certificates: %v", err)
}
pem, err := ioutil.ReadFile(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", caFile, err)
}
if !certPool.AppendCertsFromPEM(pem) {
return nil, fmt.Errorf("failed to append certificates from PEM file: %q", caFile)
}
s := certPool.Subjects()
subjects := make([]string, len(s))
for i, subject := range s {
subjects[i] = string(subject)
}
logrus.Debugf("Trusting certs with subjects: %v", subjects)
logrus.Debugf("Trusting %d certs", len(certPool.Subjects()))
return certPool, nil
}
// Client returns a TLS configuration meant to be used by a client.
func Client(options Options) (*tls.Config, error) {
tlsConfig := ClientDefault
tlsConfig := ClientDefault()
tlsConfig.InsecureSkipVerify = options.InsecureSkipVerify
if !options.InsecureSkipVerify && options.CAFile != "" {
CAs, err := certPool(options.CAFile)
@@ -101,12 +103,12 @@ func Client(options Options) (*tls.Config, error) {
tlsConfig.Certificates = []tls.Certificate{tlsCert}
}
return &tlsConfig, nil
return tlsConfig, nil
}
// Server returns a TLS configuration meant to be used by a server.
func Server(options Options) (*tls.Config, error) {
tlsConfig := ServerDefault
tlsConfig := ServerDefault()
tlsConfig.ClientAuth = options.ClientAuth
tlsCert, err := tls.LoadX509KeyPair(options.CertFile, options.KeyFile)
if err != nil {
@@ -123,5 +125,5 @@ func Server(options Options) (*tls.Config, error) {
}
tlsConfig.ClientCAs = CAs
}
return &tlsConfig, nil
return tlsConfig, nil
}