Vendoring with Glide and delete function handler

This commit is contained in:
Alex Ellis
2017-04-26 19:13:42 +01:00
parent 1eaf13c6c8
commit 78af82021f
7137 changed files with 1688302 additions and 19 deletions

View File

@ -0,0 +1,55 @@
# Contributing to Docker
### Sign your work
The sign-off is a simple line at the end of the explanation for the patch. Your
signature certifies that you wrote the patch or otherwise have the right to pass
it on as an open-source patch. The rules are pretty simple: if you can certify
the below (from [developercertificate.org](http://developercertificate.org/)):
```
Developer Certificate of Origin
Version 1.1
Copyright (C) 2004, 2006 The Linux Foundation and its contributors.
660 York Street, Suite 102,
San Francisco, CA 94110 USA
Everyone is permitted to copy and distribute verbatim copies of this
license document, but changing it is not allowed.
Developer's Certificate of Origin 1.1
By making a contribution to this project, I certify that:
(a) The contribution was created in whole or in part by me and I
have the right to submit it under the open source license
indicated in the file; or
(b) The contribution is based upon previous work that, to the best
of my knowledge, is covered under an appropriate open source
license and I have the right under that license to submit that
work with modifications, whether created in whole or in part
by me, under the same open source license (unless I am
permitted to submit under a different license), as indicated
in the file; or
(c) The contribution was provided directly to me by some other
person who certified (a), (b) or (c) and I have not modified
it.
(d) I understand and agree that this project and the contribution
are public and that a record of the contribution (including all
personal information I submit with it, including my sign-off) is
maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved.
```
Then you just add a line to every git commit message:
Signed-off-by: Joe Smith <joe.smith@email.com>
Use your real name (sorry, no pseudonyms or anonymous contributions.)
If you set your `user.name` and `user.email` git configs, you can sign your
commit automatically with `git commit -s`.

191
gateway/vendor/github.com/docker/go-connections/LICENSE generated vendored Normal file
View File

@ -0,0 +1,191 @@
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Copyright 2015 Docker, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,27 @@
# go-connections maintainers file
#
# This file describes who runs the docker/go-connections project and how.
# This is a living document - if you see something out of date or missing, speak up!
#
# It is structured to be consumable by both humans and programs.
# To extract its contents programmatically, use any TOML-compliant parser.
#
# This file is compiled into the MAINTAINERS file in docker/opensource.
#
[Org]
[Org."Core maintainers"]
people = [
"calavera",
]
[people]
# A reference list of all people associated with the project.
# All other sections should refer to people by their canonical key
# in the people section.
# ADD YOURSELF HERE IN ALPHABETICAL ORDER
[people.calavera]
Name = "David Calavera"
Email = "david.calavera@gmail.com"
GitHub = "calavera"

View File

@ -0,0 +1,13 @@
[![GoDoc](https://godoc.org/github.com/docker/go-connections?status.svg)](https://godoc.org/github.com/docker/go-connections)
# Introduction
go-connections provides common package to work with network connections.
## Usage
See the [docs in godoc](https://godoc.org/github.com/docker/go-connections) for examples and documentation.
## License
go-connections is licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for the full license text.

View File

@ -0,0 +1,14 @@
dependencies:
pre:
# setup ipv6
- sudo sysctl -w net.ipv6.conf.lo.disable_ipv6=0 net.ipv6.conf.default.disable_ipv6=0 net.ipv6.conf.all.disable_ipv6=0
post:
# install golint
- go get github.com/golang/lint/golint
test:
pre:
# run analysis before tests
- go vet ./...
- test -z "$(golint ./... | tee /dev/stderr)"
- test -z "$(gofmt -s -l . | tee /dev/stderr)"

View File

@ -0,0 +1,3 @@
// Package connections provides libraries to work with network connections.
// This library is divided in several components for specific usage.
package connections

View File

@ -0,0 +1,242 @@
// Package nat is a convenience package for manipulation of strings describing network ports.
package nat
import (
"fmt"
"net"
"strconv"
"strings"
)
const (
// portSpecTemplate is the expected format for port specifications
portSpecTemplate = "ip:hostPort:containerPort"
)
// PortBinding represents a binding between a Host IP address and a Host Port
type PortBinding struct {
// HostIP is the host IP Address
HostIP string `json:"HostIp"`
// HostPort is the host port number
HostPort string
}
// PortMap is a collection of PortBinding indexed by Port
type PortMap map[Port][]PortBinding
// PortSet is a collection of structs indexed by Port
type PortSet map[Port]struct{}
// Port is a string containing port number and protocol in the format "80/tcp"
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)
if err != nil {
return "", err
}
if portStartInt == portEndInt {
return Port(fmt.Sprintf("%d/%s", portStartInt, proto)), nil
}
return Port(fmt.Sprintf("%d-%d/%s", portStartInt, portEndInt, proto)), nil
}
// ParsePort parses the port number string and returns an int
func ParsePort(rawPort string) (int, error) {
if len(rawPort) == 0 {
return 0, nil
}
port, err := strconv.ParseUint(rawPort, 10, 16)
if err != nil {
return 0, err
}
return int(port), nil
}
// ParsePortRangeToInt parses the port range string and returns start/end ints
func ParsePortRangeToInt(rawPort string) (int, int, error) {
if len(rawPort) == 0 {
return 0, 0, nil
}
start, end, err := ParsePortRange(rawPort)
if err != nil {
return 0, 0, err
}
return int(start), int(end), nil
}
// Proto returns the protocol of a Port
func (p Port) Proto() string {
proto, _ := SplitProtoPort(string(p))
return proto
}
// Port returns the port number of a Port
func (p Port) Port() string {
_, port := SplitProtoPort(string(p))
return port
}
// Int returns the port number of a Port as an int
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)
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())
}
// SplitProtoPort splits a port in the format of proto/port
func SplitProtoPort(rawPort string) (string, string) {
parts := strings.Split(rawPort, "/")
l := len(parts)
if len(rawPort) == 0 || l == 0 || len(parts[0]) == 0 {
return "", ""
}
if l == 1 {
return "tcp", rawPort
}
if len(parts[1]) == 0 {
return "tcp", parts[0]
}
return parts[1], parts[0]
}
func validateProto(proto string) bool {
for _, availableProto := range []string{"tcp", "udp"} {
if availableProto == proto {
return true
}
}
return false
}
// ParsePortSpecs receives port specs in the format of ip:public:private/proto and parses
// these in to the internal types
func ParsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, error) {
var (
exposedPorts = make(map[Port]struct{}, len(ports))
bindings = make(map[Port][]PortBinding)
)
for _, rawPort := range ports {
portMappings, err := ParsePortSpec(rawPort)
if err != nil {
return nil, nil, err
}
for _, portMapping := range portMappings {
port := portMapping.Port
if _, exists := exposedPorts[port]; !exists {
exposedPorts[port] = struct{}{}
}
bslice, exists := bindings[port]
if !exists {
bslice = []PortBinding{}
}
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

@ -0,0 +1,583 @@
package nat
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestParsePort(t *testing.T) {
var (
p int
err error
)
p, err = ParsePort("1234")
if err != nil || p != 1234 {
t.Fatal("Parsing '1234' did not succeed")
}
// FIXME currently this is a valid port. I don't think it should be.
// I'm leaving this test commented out until we make a decision.
// - erikh
/*
p, err = ParsePort("0123")
if err != nil {
t.Fatal("Successfully parsed port '0123' to '123'")
}
*/
p, err = ParsePort("asdf")
if err == nil || p != 0 {
t.Fatal("Parsing port 'asdf' succeeded")
}
p, err = ParsePort("1asdf")
if err == nil || p != 0 {
t.Fatal("Parsing port '1asdf' succeeded")
}
}
func TestParsePortRangeToInt(t *testing.T) {
var (
begin int
end int
err error
)
type TestRange struct {
Range string
Begin int
End int
}
validRanges := []TestRange{
{"1234", 1234, 1234},
{"1234-1234", 1234, 1234},
{"1234-1235", 1234, 1235},
{"8000-9000", 8000, 9000},
{"0", 0, 0},
{"0-0", 0, 0},
}
for _, r := range validRanges {
begin, end, err = ParsePortRangeToInt(r.Range)
if err != nil || begin != r.Begin {
t.Fatalf("Parsing port range '%s' did not succeed. Expected begin %d, got %d", r.Range, r.Begin, begin)
}
if err != nil || end != r.End {
t.Fatalf("Parsing port range '%s' did not succeed. Expected end %d, got %d", r.Range, r.End, end)
}
}
invalidRanges := []string{
"asdf",
"1asdf",
"9000-8000",
"9000-",
"-8000",
"-8000-",
}
for _, r := range invalidRanges {
begin, end, err = ParsePortRangeToInt(r)
if err == nil || begin != 0 || end != 0 {
t.Fatalf("Parsing port range '%s' succeeded", r)
}
}
}
func TestPort(t *testing.T) {
p, err := NewPort("tcp", "1234")
if err != nil {
t.Fatalf("tcp, 1234 had a parsing issue: %v", err)
}
if string(p) != "1234/tcp" {
t.Fatal("tcp, 1234 did not result in the string 1234/tcp")
}
if p.Proto() != "tcp" {
t.Fatal("protocol was not tcp")
}
if p.Port() != "1234" {
t.Fatal("port string value was not 1234")
}
if p.Int() != 1234 {
t.Fatal("port int value was not 1234")
}
p, err = NewPort("tcp", "asd1234")
if err == nil {
t.Fatal("tcp, asd1234 was supposed to fail")
}
p, err = NewPort("tcp", "1234-1230")
if err == nil {
t.Fatal("tcp, 1234-1230 was supposed to fail")
}
p, err = NewPort("tcp", "1234-1242")
if err != nil {
t.Fatalf("tcp, 1234-1242 had a parsing issue: %v", err)
}
if string(p) != "1234-1242/tcp" {
t.Fatal("tcp, 1234-1242 did not result in the string 1234-1242/tcp")
}
}
func TestSplitProtoPort(t *testing.T) {
var (
proto string
port string
)
proto, port = SplitProtoPort("1234/tcp")
if proto != "tcp" || port != "1234" {
t.Fatal("Could not split 1234/tcp properly")
}
proto, port = SplitProtoPort("")
if proto != "" || port != "" {
t.Fatal("parsing an empty string yielded surprising results", proto, port)
}
proto, port = SplitProtoPort("1234")
if proto != "tcp" || port != "1234" {
t.Fatal("tcp is not the default protocol for portspec '1234'", proto, port)
}
proto, port = SplitProtoPort("1234/")
if proto != "tcp" || port != "1234" {
t.Fatal("parsing '1234/' yielded:" + port + "/" + proto)
}
proto, port = SplitProtoPort("/tcp")
if proto != "" || port != "" {
t.Fatal("parsing '/tcp' yielded:" + port + "/" + proto)
}
}
func TestParsePortSpecFull(t *testing.T) {
portMappings, err := ParsePortSpec("0.0.0.0:1234-1235:3333-3334/tcp")
assert.Nil(t, err)
expected := []PortMapping{
{
Port: "3333/tcp",
Binding: PortBinding{
HostIP: "0.0.0.0",
HostPort: "1234",
},
},
{
Port: "3334/tcp",
Binding: PortBinding{
HostIP: "0.0.0.0",
HostPort: "1235",
},
},
}
assert.Equal(t, expected, portMappings)
}
func TestPartPortSpecIPV6(t *testing.T) {
portMappings, err := ParsePortSpec("[2001:4860:0:2001::68]::333")
assert.Nil(t, err)
expected := []PortMapping{
{
Port: "333/tcp",
Binding: PortBinding{
HostIP: "2001:4860:0:2001::68",
HostPort: "",
},
},
}
assert.Equal(t, expected, portMappings)
}
func TestPartPortSpecIPV6WithHostPort(t *testing.T) {
portMappings, err := ParsePortSpec("[::1]:80:80")
assert.Nil(t, err)
expected := []PortMapping{
{
Port: "80/tcp",
Binding: PortBinding{
HostIP: "::1",
HostPort: "80",
},
},
}
assert.Equal(t, expected, portMappings)
}
func TestParsePortSpecs(t *testing.T) {
var (
portMap map[Port]struct{}
bindingMap map[Port][]PortBinding
err error
)
portMap, bindingMap, err = ParsePortSpecs([]string{"1234/tcp", "2345/udp"})
if err != nil {
t.Fatalf("Error while processing ParsePortSpecs: %s", err)
}
if _, ok := portMap[Port("1234/tcp")]; !ok {
t.Fatal("1234/tcp was not parsed properly")
}
if _, ok := portMap[Port("2345/udp")]; !ok {
t.Fatal("2345/udp was not parsed properly")
}
for portspec, bindings := range bindingMap {
if len(bindings) != 1 {
t.Fatalf("%s should have exactly one binding", portspec)
}
if bindings[0].HostIP != "" {
t.Fatalf("HostIP should not be set for %s", portspec)
}
if bindings[0].HostPort != "" {
t.Fatalf("HostPort should not be set for %s", portspec)
}
}
portMap, bindingMap, err = ParsePortSpecs([]string{"1234:1234/tcp", "2345:2345/udp"})
if err != nil {
t.Fatalf("Error while processing ParsePortSpecs: %s", err)
}
if _, ok := portMap[Port("1234/tcp")]; !ok {
t.Fatal("1234/tcp was not parsed properly")
}
if _, ok := portMap[Port("2345/udp")]; !ok {
t.Fatal("2345/udp was not parsed properly")
}
for portspec, bindings := range bindingMap {
_, port := SplitProtoPort(string(portspec))
if len(bindings) != 1 {
t.Fatalf("%s should have exactly one binding", portspec)
}
if bindings[0].HostIP != "" {
t.Fatalf("HostIP should not be set for %s", portspec)
}
if bindings[0].HostPort != port {
t.Fatalf("HostPort should be %s for %s", port, portspec)
}
}
portMap, bindingMap, err = ParsePortSpecs([]string{"0.0.0.0:1234:1234/tcp", "0.0.0.0:2345:2345/udp"})
if err != nil {
t.Fatalf("Error while processing ParsePortSpecs: %s", err)
}
if _, ok := portMap[Port("1234/tcp")]; !ok {
t.Fatal("1234/tcp was not parsed properly")
}
if _, ok := portMap[Port("2345/udp")]; !ok {
t.Fatal("2345/udp was not parsed properly")
}
for portspec, bindings := range bindingMap {
_, port := SplitProtoPort(string(portspec))
if len(bindings) != 1 {
t.Fatalf("%s should have exactly one binding", portspec)
}
if bindings[0].HostIP != "0.0.0.0" {
t.Fatalf("HostIP is not 0.0.0.0 for %s", portspec)
}
if bindings[0].HostPort != port {
t.Fatalf("HostPort should be %s for %s", port, portspec)
}
}
_, _, err = ParsePortSpecs([]string{"localhost:1234:1234/tcp"})
if err == nil {
t.Fatal("Received no error while trying to parse a hostname instead of ip")
}
}
func TestParsePortSpecsWithRange(t *testing.T) {
var (
portMap map[Port]struct{}
bindingMap map[Port][]PortBinding
err error
)
portMap, bindingMap, err = ParsePortSpecs([]string{"1234-1236/tcp", "2345-2347/udp"})
if err != nil {
t.Fatalf("Error while processing ParsePortSpecs: %s", err)
}
if _, ok := portMap[Port("1235/tcp")]; !ok {
t.Fatal("1234/tcp was not parsed properly")
}
if _, ok := portMap[Port("2346/udp")]; !ok {
t.Fatal("2345/udp was not parsed properly")
}
for portspec, bindings := range bindingMap {
if len(bindings) != 1 {
t.Fatalf("%s should have exactly one binding", portspec)
}
if bindings[0].HostIP != "" {
t.Fatalf("HostIP should not be set for %s", portspec)
}
if bindings[0].HostPort != "" {
t.Fatalf("HostPort should not be set for %s", portspec)
}
}
portMap, bindingMap, err = ParsePortSpecs([]string{"1234-1236:1234-1236/tcp", "2345-2347:2345-2347/udp"})
if err != nil {
t.Fatalf("Error while processing ParsePortSpecs: %s", err)
}
if _, ok := portMap[Port("1235/tcp")]; !ok {
t.Fatal("1234/tcp was not parsed properly")
}
if _, ok := portMap[Port("2346/udp")]; !ok {
t.Fatal("2345/udp was not parsed properly")
}
for portspec, bindings := range bindingMap {
_, port := SplitProtoPort(string(portspec))
if len(bindings) != 1 {
t.Fatalf("%s should have exactly one binding", portspec)
}
if bindings[0].HostIP != "" {
t.Fatalf("HostIP should not be set for %s", portspec)
}
if bindings[0].HostPort != port {
t.Fatalf("HostPort should be %s for %s", port, portspec)
}
}
portMap, bindingMap, err = ParsePortSpecs([]string{"0.0.0.0:1234-1236:1234-1236/tcp", "0.0.0.0:2345-2347:2345-2347/udp"})
if err != nil {
t.Fatalf("Error while processing ParsePortSpecs: %s", err)
}
if _, ok := portMap[Port("1235/tcp")]; !ok {
t.Fatal("1234/tcp was not parsed properly")
}
if _, ok := portMap[Port("2346/udp")]; !ok {
t.Fatal("2345/udp was not parsed properly")
}
for portspec, bindings := range bindingMap {
_, port := SplitProtoPort(string(portspec))
if len(bindings) != 1 || bindings[0].HostIP != "0.0.0.0" || bindings[0].HostPort != port {
t.Fatalf("Expect single binding to port %s but found %s", port, bindings)
}
}
_, _, err = ParsePortSpecs([]string{"localhost:1234-1236:1234-1236/tcp"})
if err == nil {
t.Fatal("Received no error while trying to parse a hostname instead of ip")
}
}
func TestParseNetworkOptsPrivateOnly(t *testing.T) {
ports, bindings, err := ParsePortSpecs([]string{"192.168.1.100::80"})
if err != nil {
t.Fatal(err)
}
if len(ports) != 1 {
t.Logf("Expected 1 got %d", len(ports))
t.FailNow()
}
if len(bindings) != 1 {
t.Logf("Expected 1 got %d", len(bindings))
t.FailNow()
}
for k := range ports {
if k.Proto() != "tcp" {
t.Logf("Expected tcp got %s", k.Proto())
t.Fail()
}
if k.Port() != "80" {
t.Logf("Expected 80 got %s", k.Port())
t.Fail()
}
b, exists := bindings[k]
if !exists {
t.Log("Binding does not exist")
t.FailNow()
}
if len(b) != 1 {
t.Logf("Expected 1 got %d", len(b))
t.FailNow()
}
s := b[0]
if s.HostPort != "" {
t.Logf("Expected \"\" got %s", s.HostPort)
t.Fail()
}
if s.HostIP != "192.168.1.100" {
t.Fail()
}
}
}
func TestParseNetworkOptsPublic(t *testing.T) {
ports, bindings, err := ParsePortSpecs([]string{"192.168.1.100:8080:80"})
if err != nil {
t.Fatal(err)
}
if len(ports) != 1 {
t.Logf("Expected 1 got %d", len(ports))
t.FailNow()
}
if len(bindings) != 1 {
t.Logf("Expected 1 got %d", len(bindings))
t.FailNow()
}
for k := range ports {
if k.Proto() != "tcp" {
t.Logf("Expected tcp got %s", k.Proto())
t.Fail()
}
if k.Port() != "80" {
t.Logf("Expected 80 got %s", k.Port())
t.Fail()
}
b, exists := bindings[k]
if !exists {
t.Log("Binding does not exist")
t.FailNow()
}
if len(b) != 1 {
t.Logf("Expected 1 got %d", len(b))
t.FailNow()
}
s := b[0]
if s.HostPort != "8080" {
t.Logf("Expected 8080 got %s", s.HostPort)
t.Fail()
}
if s.HostIP != "192.168.1.100" {
t.Fail()
}
}
}
func TestParseNetworkOptsPublicNoPort(t *testing.T) {
ports, bindings, err := ParsePortSpecs([]string{"192.168.1.100"})
if err == nil {
t.Logf("Expected error Invalid containerPort")
t.Fail()
}
if ports != nil {
t.Logf("Expected nil got %s", ports)
t.Fail()
}
if bindings != nil {
t.Logf("Expected nil got %s", bindings)
t.Fail()
}
}
func TestParseNetworkOptsNegativePorts(t *testing.T) {
ports, bindings, err := ParsePortSpecs([]string{"192.168.1.100:-1:-1"})
if err == nil {
t.Fail()
}
if len(ports) != 0 {
t.Logf("Expected nil got %d", len(ports))
t.Fail()
}
if len(bindings) != 0 {
t.Logf("Expected 0 got %d", len(bindings))
t.Fail()
}
}
func TestParseNetworkOptsUdp(t *testing.T) {
ports, bindings, err := ParsePortSpecs([]string{"192.168.1.100::6000/udp"})
if err != nil {
t.Fatal(err)
}
if len(ports) != 1 {
t.Logf("Expected 1 got %d", len(ports))
t.FailNow()
}
if len(bindings) != 1 {
t.Logf("Expected 1 got %d", len(bindings))
t.FailNow()
}
for k := range ports {
if k.Proto() != "udp" {
t.Logf("Expected udp got %s", k.Proto())
t.Fail()
}
if k.Port() != "6000" {
t.Logf("Expected 6000 got %s", k.Port())
t.Fail()
}
b, exists := bindings[k]
if !exists {
t.Log("Binding does not exist")
t.FailNow()
}
if len(b) != 1 {
t.Logf("Expected 1 got %d", len(b))
t.FailNow()
}
s := b[0]
if s.HostPort != "" {
t.Logf("Expected \"\" got %s", s.HostPort)
t.Fail()
}
if s.HostIP != "192.168.1.100" {
t.Fail()
}
}
}

View File

@ -0,0 +1,57 @@
package nat
import (
"fmt"
"strconv"
"strings"
)
// 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 (
templateParts = strings.Split(template, ":")
parts = strings.Split(data, ":")
out = make(map[string]string, len(templateParts))
)
if len(parts) != len(templateParts) {
return nil, fmt.Errorf("Invalid format to parse. %s should match template %s", data, template)
}
for i, t := range templateParts {
value := ""
if len(parts) > i {
value = parts[i]
}
out[t] = value
}
return out, nil
}
// ParsePortRange parses and validates the specified string as a port-range (8000-9000)
func ParsePortRange(ports string) (uint64, uint64, error) {
if ports == "" {
return 0, 0, fmt.Errorf("Empty string specified for ports.")
}
if !strings.Contains(ports, "-") {
start, err := strconv.ParseUint(ports, 10, 16)
end := start
return start, end, err
}
parts := strings.Split(ports, "-")
start, err := strconv.ParseUint(parts[0], 10, 16)
if err != nil {
return 0, 0, err
}
end, err := strconv.ParseUint(parts[1], 10, 16)
if err != nil {
return 0, 0, err
}
if end < start {
return 0, 0, fmt.Errorf("Invalid range specified for the Port: %s", ports)
}
return start, end, nil
}

View File

@ -0,0 +1,54 @@
package nat
import (
"strings"
"testing"
)
func TestParsePortRange(t *testing.T) {
if start, end, err := ParsePortRange("8000-8080"); err != nil || start != 8000 || end != 8080 {
t.Fatalf("Error: %s or Expecting {start,end} values {8000,8080} but found {%d,%d}.", err, start, end)
}
}
func TestParsePortRangeEmpty(t *testing.T) {
if _, _, err := ParsePortRange(""); err == nil || err.Error() != "Empty string specified for ports." {
t.Fatalf("Expected error 'Empty string specified for ports.', got %v", err)
}
}
func TestParsePortRangeWithNoRange(t *testing.T) {
start, end, err := ParsePortRange("8080")
if err != nil {
t.Fatal(err)
}
if start != 8080 || end != 8080 {
t.Fatalf("Expected start and end to be the same and equal to 8080, but were %v and %v", start, end)
}
}
func TestParsePortRangeIncorrectRange(t *testing.T) {
if _, _, err := ParsePortRange("9000-8080"); err == nil || !strings.Contains(err.Error(), "Invalid range specified for the Port") {
t.Fatalf("Expecting error 'Invalid range specified for the Port' but received %s.", err)
}
}
func TestParsePortRangeIncorrectEndRange(t *testing.T) {
if _, _, err := ParsePortRange("8000-a"); err == nil || !strings.Contains(err.Error(), "invalid syntax") {
t.Fatalf("Expecting error 'Invalid range specified for the Port' but received %s.", err)
}
if _, _, err := ParsePortRange("8000-30a"); err == nil || !strings.Contains(err.Error(), "invalid syntax") {
t.Fatalf("Expecting error 'Invalid range specified for the Port' but received %s.", err)
}
}
func TestParsePortRangeIncorrectStartRange(t *testing.T) {
if _, _, err := ParsePortRange("a-8000"); err == nil || !strings.Contains(err.Error(), "invalid syntax") {
t.Fatalf("Expecting error 'Invalid range specified for the Port' but received %s.", err)
}
if _, _, err := ParsePortRange("30a-8000"); err == nil || !strings.Contains(err.Error(), "invalid syntax") {
t.Fatalf("Expecting error 'Invalid range specified for the Port' but received %s.", err)
}
}

View File

@ -0,0 +1,96 @@
package nat
import (
"sort"
"strings"
)
type portSorter struct {
ports []Port
by func(i, j Port) bool
}
func (s *portSorter) Len() int {
return len(s.ports)
}
func (s *portSorter) Swap(i, j int) {
s.ports[i], s.ports[j] = s.ports[j], s.ports[i]
}
func (s *portSorter) Less(i, j int) bool {
ip := s.ports[i]
jp := s.ports[j]
return s.by(ip, jp)
}
// Sort sorts a list of ports using the provided predicate
// This function should compare `i` and `j`, returning true if `i` is
// considered to be less than `j`
func Sort(ports []Port, predicate func(i, j Port) bool) {
s := &portSorter{ports, predicate}
sort.Sort(s)
}
type portMapEntry struct {
port Port
binding PortBinding
}
type portMapSorter []portMapEntry
func (s portMapSorter) Len() int { return len(s) }
func (s portMapSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
// sort the port so that the order is:
// 1. port with larger specified bindings
// 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")
}
// 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) {
s := portMapSorter{}
for _, p := range ports {
if binding, ok := bindings[p]; ok {
for _, b := range binding {
s = append(s, portMapEntry{port: p, binding: b})
}
bindings[p] = []PortBinding{}
} else {
s = append(s, portMapEntry{port: p})
}
}
sort.Sort(s)
var (
i int
pm = make(map[Port]struct{})
)
// reorder ports
for _, entry := range s {
if _, ok := pm[entry.port]; !ok {
ports[i] = entry.port
pm[entry.port] = struct{}{}
i++
}
// reorder bindings for this port
if _, ok := bindings[entry.port]; ok {
bindings[entry.port] = append(bindings[entry.port], entry.binding)
}
}
}
func toInt(s string) uint64 {
i, _, err := ParsePortRange(s)
if err != nil {
i = 0
}
return i
}

View File

@ -0,0 +1,85 @@
package nat
import (
"fmt"
"reflect"
"testing"
)
func TestSortUniquePorts(t *testing.T) {
ports := []Port{
Port("6379/tcp"),
Port("22/tcp"),
}
Sort(ports, func(ip, jp Port) bool {
return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && ip.Proto() == "tcp")
})
first := ports[0]
if fmt.Sprint(first) != "22/tcp" {
t.Log(fmt.Sprint(first))
t.Fail()
}
}
func TestSortSamePortWithDifferentProto(t *testing.T) {
ports := []Port{
Port("8888/tcp"),
Port("8888/udp"),
Port("6379/tcp"),
Port("6379/udp"),
}
Sort(ports, func(ip, jp Port) bool {
return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && ip.Proto() == "tcp")
})
first := ports[0]
if fmt.Sprint(first) != "6379/tcp" {
t.Fail()
}
}
func TestSortPortMap(t *testing.T) {
ports := []Port{
Port("22/tcp"),
Port("22/udp"),
Port("8000/tcp"),
Port("6379/tcp"),
Port("9999/tcp"),
}
portMap := PortMap{
Port("22/tcp"): []PortBinding{
{},
},
Port("8000/tcp"): []PortBinding{
{},
},
Port("6379/tcp"): []PortBinding{
{},
{HostIP: "0.0.0.0", HostPort: "32749"},
},
Port("9999/tcp"): []PortBinding{
{HostIP: "0.0.0.0", HostPort: "40000"},
},
}
SortPortMap(ports, portMap)
if !reflect.DeepEqual(ports, []Port{
Port("9999/tcp"),
Port("6379/tcp"),
Port("8000/tcp"),
Port("22/tcp"),
Port("22/udp"),
}) {
t.Errorf("failed to prioritize port with explicit mappings, got %v", ports)
}
if pm := portMap[Port("6379/tcp")]; !reflect.DeepEqual(pm, []PortBinding{
{HostIP: "0.0.0.0", HostPort: "32749"},
{},
}) {
t.Errorf("failed to prioritize bindings with explicit mappings, got %v", pm)
}
}

View File

@ -0,0 +1,216 @@
package proxy
import (
"bytes"
"fmt"
"io"
"net"
"strings"
"testing"
"time"
)
var testBuf = []byte("Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo")
var testBufSize = len(testBuf)
type EchoServer interface {
Run()
Close()
LocalAddr() net.Addr
}
type TCPEchoServer struct {
listener net.Listener
testCtx *testing.T
}
type UDPEchoServer struct {
conn net.PacketConn
testCtx *testing.T
}
func NewEchoServer(t *testing.T, proto, address string) EchoServer {
var server EchoServer
if strings.HasPrefix(proto, "tcp") {
listener, err := net.Listen(proto, address)
if err != nil {
t.Fatal(err)
}
server = &TCPEchoServer{listener: listener, testCtx: t}
} else {
socket, err := net.ListenPacket(proto, address)
if err != nil {
t.Fatal(err)
}
server = &UDPEchoServer{conn: socket, testCtx: t}
}
return server
}
func (server *TCPEchoServer) Run() {
go func() {
for {
client, err := server.listener.Accept()
if err != nil {
return
}
go func(client net.Conn) {
if _, err := io.Copy(client, client); err != nil {
server.testCtx.Logf("can't echo to the client: %v\n", err.Error())
}
client.Close()
}(client)
}
}()
}
func (server *TCPEchoServer) LocalAddr() net.Addr { return server.listener.Addr() }
func (server *TCPEchoServer) Close() { server.listener.Close() }
func (server *UDPEchoServer) Run() {
go func() {
readBuf := make([]byte, 1024)
for {
read, from, err := server.conn.ReadFrom(readBuf)
if err != nil {
return
}
for i := 0; i != read; {
written, err := server.conn.WriteTo(readBuf[i:read], from)
if err != nil {
break
}
i += written
}
}
}()
}
func (server *UDPEchoServer) LocalAddr() net.Addr { return server.conn.LocalAddr() }
func (server *UDPEchoServer) Close() { server.conn.Close() }
func testProxyAt(t *testing.T, proto string, proxy Proxy, addr string) {
defer proxy.Close()
go proxy.Run()
client, err := net.Dial(proto, addr)
if err != nil {
t.Fatalf("Can't connect to the proxy: %v", err)
}
defer client.Close()
client.SetDeadline(time.Now().Add(10 * time.Second))
if _, err = client.Write(testBuf); err != nil {
t.Fatal(err)
}
recvBuf := make([]byte, testBufSize)
if _, err = client.Read(recvBuf); err != nil {
t.Fatal(err)
}
if !bytes.Equal(testBuf, recvBuf) {
t.Fatal(fmt.Errorf("Expected [%v] but got [%v]", testBuf, recvBuf))
}
}
func testProxy(t *testing.T, proto string, proxy Proxy) {
testProxyAt(t, proto, proxy, proxy.FrontendAddr().String())
}
func TestTCP4Proxy(t *testing.T) {
backend := NewEchoServer(t, "tcp", "127.0.0.1:0")
defer backend.Close()
backend.Run()
frontendAddr := &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}
proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
if err != nil {
t.Fatal(err)
}
testProxy(t, "tcp", proxy)
}
func TestTCP6Proxy(t *testing.T) {
backend := NewEchoServer(t, "tcp", "[::1]:0")
defer backend.Close()
backend.Run()
frontendAddr := &net.TCPAddr{IP: net.IPv6loopback, Port: 0}
proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
if err != nil {
t.Fatal(err)
}
testProxy(t, "tcp", proxy)
}
func TestTCPDualStackProxy(t *testing.T) {
// If I understand `godoc -src net favoriteAddrFamily` (used by the
// net.Listen* functions) correctly this should work, but it doesn't.
t.Skip("No support for dual stack yet")
backend := NewEchoServer(t, "tcp", "[::1]:0")
defer backend.Close()
backend.Run()
frontendAddr := &net.TCPAddr{IP: net.IPv6loopback, Port: 0}
proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
if err != nil {
t.Fatal(err)
}
ipv4ProxyAddr := &net.TCPAddr{
IP: net.IPv4(127, 0, 0, 1),
Port: proxy.FrontendAddr().(*net.TCPAddr).Port,
}
testProxyAt(t, "tcp", proxy, ipv4ProxyAddr.String())
}
func TestUDP4Proxy(t *testing.T) {
backend := NewEchoServer(t, "udp", "127.0.0.1:0")
defer backend.Close()
backend.Run()
frontendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}
proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
if err != nil {
t.Fatal(err)
}
testProxy(t, "udp", proxy)
}
func TestUDP6Proxy(t *testing.T) {
backend := NewEchoServer(t, "udp", "[::1]:0")
defer backend.Close()
backend.Run()
frontendAddr := &net.UDPAddr{IP: net.IPv6loopback, Port: 0}
proxy, err := NewProxy(frontendAddr, backend.LocalAddr())
if err != nil {
t.Fatal(err)
}
testProxy(t, "udp", proxy)
}
func TestUDPWriteError(t *testing.T) {
frontendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 0}
// Hopefully, this port will be free: */
backendAddr := &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 25587}
proxy, err := NewProxy(frontendAddr, backendAddr)
if err != nil {
t.Fatal(err)
}
defer proxy.Close()
go proxy.Run()
client, err := net.Dial("udp", "127.0.0.1:25587")
if err != nil {
t.Fatalf("Can't connect to the proxy: %v", err)
}
defer client.Close()
// Make sure the proxy doesn't stop when there is no actual backend:
client.Write(testBuf)
client.Write(testBuf)
backend := NewEchoServer(t, "udp", "127.0.0.1:25587")
defer backend.Close()
backend.Run()
client.SetDeadline(time.Now().Add(10 * time.Second))
if _, err = client.Write(testBuf); err != nil {
t.Fatal(err)
}
recvBuf := make([]byte, testBufSize)
if _, err = client.Read(recvBuf); err != nil {
t.Fatal(err)
}
if !bytes.Equal(testBuf, recvBuf) {
t.Fatal(fmt.Errorf("Expected [%v] but got [%v]", testBuf, recvBuf))
}
}

View File

@ -0,0 +1,36 @@
// Package proxy provides a network Proxy interface and implementations for TCP and UDP.
package proxy
import (
"fmt"
"net"
)
// Proxy defines the behavior of a proxy. It forwards traffic back and forth
// between two endpoints : the frontend and the backend.
// It can be used to do software port-mapping between two addresses.
// e.g. forward all traffic between the frontend (host) 127.0.0.1:3000
// to the backend (container) at 172.17.42.108:4000.
type Proxy interface {
// Run starts forwarding traffic back and forth between the front
// and back-end addresses.
Run()
// Close stops forwarding traffic and close both ends of the Proxy.
Close()
// FrontendAddr returns the address on which the proxy is listening.
FrontendAddr() net.Addr
// BackendAddr returns the proxied address.
BackendAddr() net.Addr
}
// NewProxy creates a Proxy according to the specified frontendAddr and backendAddr.
func NewProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) {
switch frontendAddr.(type) {
case *net.UDPAddr:
return NewUDPProxy(frontendAddr.(*net.UDPAddr), backendAddr.(*net.UDPAddr))
case *net.TCPAddr:
return NewTCPProxy(frontendAddr.(*net.TCPAddr), backendAddr.(*net.TCPAddr))
default:
panic(fmt.Errorf("Unsupported protocol"))
}
}

View File

@ -0,0 +1,31 @@
package proxy
import (
"net"
)
// StubProxy is a proxy that is a stub (does nothing).
type StubProxy struct {
frontendAddr net.Addr
backendAddr net.Addr
}
// Run does nothing.
func (p *StubProxy) Run() {}
// Close does nothing.
func (p *StubProxy) Close() {}
// FrontendAddr returns the frontend address.
func (p *StubProxy) FrontendAddr() net.Addr { return p.frontendAddr }
// BackendAddr returns the backend address.
func (p *StubProxy) BackendAddr() net.Addr { return p.backendAddr }
// NewStubProxy creates a new StubProxy
func NewStubProxy(frontendAddr, backendAddr net.Addr) (Proxy, error) {
return &StubProxy{
frontendAddr: frontendAddr,
backendAddr: backendAddr,
}, nil
}

View File

@ -0,0 +1,99 @@
package proxy
import (
"io"
"net"
"syscall"
"github.com/Sirupsen/logrus"
)
// TCPProxy is a proxy for TCP connections. It implements the Proxy interface to
// handle TCP traffic forwarding between the frontend and backend addresses.
type TCPProxy struct {
listener *net.TCPListener
frontendAddr *net.TCPAddr
backendAddr *net.TCPAddr
}
// NewTCPProxy creates a new TCPProxy.
func NewTCPProxy(frontendAddr, backendAddr *net.TCPAddr) (*TCPProxy, error) {
listener, err := net.ListenTCP("tcp", frontendAddr)
if err != nil {
return nil, err
}
// If the port in frontendAddr was 0 then ListenTCP will have a picked
// a port to listen on, hence the call to Addr to get that actual port:
return &TCPProxy{
listener: listener,
frontendAddr: listener.Addr().(*net.TCPAddr),
backendAddr: backendAddr,
}, nil
}
func (proxy *TCPProxy) clientLoop(client *net.TCPConn, quit chan bool) {
backend, err := net.DialTCP("tcp", nil, proxy.backendAddr)
if err != nil {
logrus.Printf("Can't forward traffic to backend tcp/%v: %s\n", proxy.backendAddr, err)
client.Close()
return
}
event := make(chan int64)
var broker = func(to, from *net.TCPConn) {
written, err := io.Copy(to, from)
if err != nil {
// If the socket we are writing to is shutdown with
// SHUT_WR, forward it to the other end of the pipe:
if err, ok := err.(*net.OpError); ok && err.Err == syscall.EPIPE {
from.CloseWrite()
}
}
to.CloseRead()
event <- written
}
go broker(client, backend)
go broker(backend, client)
var transferred int64
for i := 0; i < 2; i++ {
select {
case written := <-event:
transferred += written
case <-quit:
// Interrupt the two brokers and "join" them.
client.Close()
backend.Close()
for ; i < 2; i++ {
transferred += <-event
}
return
}
}
client.Close()
backend.Close()
}
// Run starts forwarding the traffic using TCP.
func (proxy *TCPProxy) Run() {
quit := make(chan bool)
defer close(quit)
for {
client, err := proxy.listener.Accept()
if err != nil {
logrus.Printf("Stopping proxy on tcp/%v for tcp/%v (%s)", proxy.frontendAddr, proxy.backendAddr, err)
return
}
go proxy.clientLoop(client.(*net.TCPConn), quit)
}
}
// Close stops forwarding the traffic.
func (proxy *TCPProxy) Close() { proxy.listener.Close() }
// FrontendAddr returns the TCP address on which the proxy is listening.
func (proxy *TCPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr }
// BackendAddr returns the TCP proxied address.
func (proxy *TCPProxy) BackendAddr() net.Addr { return proxy.backendAddr }

View File

@ -0,0 +1,169 @@
package proxy
import (
"encoding/binary"
"net"
"strings"
"sync"
"syscall"
"time"
"github.com/Sirupsen/logrus"
)
const (
// UDPConnTrackTimeout is the timeout used for UDP connection tracking
UDPConnTrackTimeout = 90 * time.Second
// UDPBufSize is the buffer size for the UDP proxy
UDPBufSize = 65507
)
// A net.Addr where the IP is split into two fields so you can use it as a key
// in a map:
type connTrackKey struct {
IPHigh uint64
IPLow uint64
Port int
}
func newConnTrackKey(addr *net.UDPAddr) *connTrackKey {
if len(addr.IP) == net.IPv4len {
return &connTrackKey{
IPHigh: 0,
IPLow: uint64(binary.BigEndian.Uint32(addr.IP)),
Port: addr.Port,
}
}
return &connTrackKey{
IPHigh: binary.BigEndian.Uint64(addr.IP[:8]),
IPLow: binary.BigEndian.Uint64(addr.IP[8:]),
Port: addr.Port,
}
}
type connTrackMap map[connTrackKey]*net.UDPConn
// UDPProxy is proxy for which handles UDP datagrams. It implements the Proxy
// interface to handle UDP traffic forwarding between the frontend and backend
// addresses.
type UDPProxy struct {
listener *net.UDPConn
frontendAddr *net.UDPAddr
backendAddr *net.UDPAddr
connTrackTable connTrackMap
connTrackLock sync.Mutex
}
// NewUDPProxy creates a new UDPProxy.
func NewUDPProxy(frontendAddr, backendAddr *net.UDPAddr) (*UDPProxy, error) {
listener, err := net.ListenUDP("udp", frontendAddr)
if err != nil {
return nil, err
}
return &UDPProxy{
listener: listener,
frontendAddr: listener.LocalAddr().(*net.UDPAddr),
backendAddr: backendAddr,
connTrackTable: make(connTrackMap),
}, nil
}
func (proxy *UDPProxy) replyLoop(proxyConn *net.UDPConn, clientAddr *net.UDPAddr, clientKey *connTrackKey) {
defer func() {
proxy.connTrackLock.Lock()
delete(proxy.connTrackTable, *clientKey)
proxy.connTrackLock.Unlock()
proxyConn.Close()
}()
readBuf := make([]byte, UDPBufSize)
for {
proxyConn.SetReadDeadline(time.Now().Add(UDPConnTrackTimeout))
again:
read, err := proxyConn.Read(readBuf)
if err != nil {
if err, ok := err.(*net.OpError); ok && err.Err == syscall.ECONNREFUSED {
// This will happen if the last write failed
// (e.g: nothing is actually listening on the
// proxied port on the container), ignore it
// and continue until UDPConnTrackTimeout
// expires:
goto again
}
return
}
for i := 0; i != read; {
written, err := proxy.listener.WriteToUDP(readBuf[i:read], clientAddr)
if err != nil {
return
}
i += written
}
}
}
// Run starts forwarding the traffic using UDP.
func (proxy *UDPProxy) Run() {
readBuf := make([]byte, UDPBufSize)
for {
read, from, err := proxy.listener.ReadFromUDP(readBuf)
if err != nil {
// NOTE: Apparently ReadFrom doesn't return
// ECONNREFUSED like Read do (see comment in
// UDPProxy.replyLoop)
if !isClosedError(err) {
logrus.Printf("Stopping proxy on udp/%v for udp/%v (%s)", proxy.frontendAddr, proxy.backendAddr, err)
}
break
}
fromKey := newConnTrackKey(from)
proxy.connTrackLock.Lock()
proxyConn, hit := proxy.connTrackTable[*fromKey]
if !hit {
proxyConn, err = net.DialUDP("udp", nil, proxy.backendAddr)
if err != nil {
logrus.Printf("Can't proxy a datagram to udp/%s: %s\n", proxy.backendAddr, err)
proxy.connTrackLock.Unlock()
continue
}
proxy.connTrackTable[*fromKey] = proxyConn
go proxy.replyLoop(proxyConn, from, fromKey)
}
proxy.connTrackLock.Unlock()
for i := 0; i != read; {
written, err := proxyConn.Write(readBuf[i:read])
if err != nil {
logrus.Printf("Can't proxy a datagram to udp/%s: %s\n", proxy.backendAddr, err)
break
}
i += written
}
}
}
// Close stops forwarding the traffic.
func (proxy *UDPProxy) Close() {
proxy.listener.Close()
proxy.connTrackLock.Lock()
defer proxy.connTrackLock.Unlock()
for _, conn := range proxy.connTrackTable {
conn.Close()
}
}
// FrontendAddr returns the UDP address on which the proxy is listening.
func (proxy *UDPProxy) FrontendAddr() net.Addr { return proxy.frontendAddr }
// BackendAddr returns the proxied UDP address.
func (proxy *UDPProxy) BackendAddr() net.Addr { return proxy.backendAddr }
func isClosedError(err error) bool {
/* This comparison is ugly, but unfortunately, net.go doesn't export errClosing.
* See:
* http://golang.org/src/pkg/net/net.go
* https://code.google.com/p/go/issues/detail?id=4337
* https://groups.google.com/forum/#!msg/golang-nuts/0_aaCvBmOcM/SptmDyX1XJMJ
*/
return strings.HasSuffix(err.Error(), "use of closed network connection")
}

View File

View File

@ -0,0 +1,81 @@
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
func NewInmemSocket(addr string, bufSize int) *InmemSocket {
return &InmemSocket{
chConn: make(chan net.Conn, bufSize),
chClose: make(chan struct{}),
addr: addr,
}
}
// Addr returns the socket's addr string to satisfy net.Listener
func (s *InmemSocket) Addr() net.Addr {
return dummyAddr(s.addr)
}
// Accept implements the Accept method in the Listener interface; it waits for the next call and returns a generic Conn.
func (s *InmemSocket) Accept() (net.Conn, error) {
select {
case conn := <-s.chConn:
return conn, nil
case <-s.chClose:
return nil, errClosed
}
}
// Close closes the listener. It will be unavailable for use once closed.
func (s *InmemSocket) Close() error {
s.mu.Lock()
defer s.mu.Unlock()
select {
case <-s.chClose:
default:
close(s.chClose)
}
return nil
}
// Dial is used to establish a connection with the in-mem server
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 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)
}

View File

@ -0,0 +1,39 @@
package sockets
import "testing"
func TestInmemSocket(t *testing.T) {
l := NewInmemSocket("test", 0)
defer l.Close()
go func() {
for {
conn, err := l.Accept()
if err != nil {
return
}
conn.Write([]byte("hello"))
conn.Close()
}
}()
conn, err := l.Dial("test", "test")
if err != nil {
t.Fatal(err)
}
buf := make([]byte, 5)
_, err = conn.Read(buf)
if err != nil {
t.Fatal(err)
}
if string(buf) != "hello" {
t.Fatalf("expected `hello`, got %s", string(buf))
}
l.Close()
conn, err = l.Dial("test", "test")
if err != errClosed {
t.Fatalf("expected `errClosed` error, got %v", err)
}
}

View File

@ -0,0 +1,51 @@
package sockets
import (
"net"
"net/url"
"os"
"strings"
"golang.org/x/net/proxy"
)
// 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/
func GetProxyEnv(key string) string {
proxyValue := os.Getenv(strings.ToUpper(key))
if proxyValue == "" {
return os.Getenv(strings.ToLower(key))
}
return proxyValue
}
// DialerFromEnvironment takes in a "direct" *net.Dialer and returns a
// proxy.Dialer which will route the connections through the proxy using the
// given dialer.
func DialerFromEnvironment(direct *net.Dialer) (proxy.Dialer, error) {
allProxy := GetProxyEnv("all_proxy")
if len(allProxy) == 0 {
return direct, nil
}
proxyURL, err := url.Parse(allProxy)
if err != nil {
return direct, err
}
proxyFromURL, err := proxy.FromURL(proxyURL, direct)
if err != nil {
return direct, err
}
noProxy := GetProxyEnv("no_proxy")
if len(noProxy) == 0 {
return proxyFromURL, nil
}
perHost := proxy.NewPerHost(proxyFromURL, direct)
perHost.AddFromString(noProxy)
return perHost, nil
}

View File

@ -0,0 +1,38 @@
// Package sockets provides helper functions to create and configure Unix or TCP sockets.
package sockets
import (
"errors"
"net"
"net/http"
"time"
)
// 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
// compression is disabled.
func ConfigureTransport(tr *http.Transport, proto, addr string) error {
switch proto {
case "unix":
return configureUnixTransport(tr, proto, addr)
case "npipe":
return configureNpipeTransport(tr, proto, addr)
default:
tr.Proxy = http.ProxyFromEnvironment
dialer, err := DialerFromEnvironment(&net.Dialer{
Timeout: defaultTimeout,
})
if err != nil {
return err
}
tr.Dial = dialer.Dial
}
return nil
}

View File

@ -0,0 +1,35 @@
// +build !windows
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) {
return nil, syscall.EAFNOSUPPORT
}

View File

@ -0,0 +1,27 @@
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

@ -0,0 +1,22 @@
// Package sockets provides helper functions to create and configure Unix or TCP sockets.
package sockets
import (
"crypto/tls"
"net"
)
// NewTCPSocket creates a TCP socket listener with the specified address and
// 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)
if err != nil {
return nil, err
}
if tlsConfig != nil {
tlsConfig.NextProtos = []string{"http/1.1"}
l = tls.NewListener(l, tlsConfig)
}
return l, nil
}

View File

@ -0,0 +1,32 @@
// +build !windows
package sockets
import (
"net"
"os"
"syscall"
)
// NewUnixSocket creates a unix socket with the specified path and group.
func NewUnixSocket(path string, gid int) (net.Listener, error) {
if err := syscall.Unlink(path); err != nil && !os.IsNotExist(err) {
return nil, err
}
mask := syscall.Umask(0777)
defer syscall.Umask(mask)
l, err := net.Listen("unix", path)
if err != nil {
return nil, err
}
if err := os.Chown(path, 0, gid); err != nil {
l.Close()
return nil, err
}
if err := os.Chmod(path, 0660); err != nil {
l.Close()
return nil, err
}
return l, nil
}

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.Infof("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

@ -0,0 +1,246 @@
// Package tlsconfig provides primitives to retrieve secure-enough TLS configurations for both clients and servers.
//
// As a reminder from https://golang.org/pkg/crypto/tls/#Config:
// A Config structure is used to configure a TLS client or server. After one has been passed to a TLS function it must not be modified.
// A Config may be reused; the tls package will also not modify it.
package tlsconfig
import (
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"io/ioutil"
"os"
"github.com/Sirupsen/logrus"
"github.com/pkg/errors"
)
// Options represents the information needed to create client and server TLS configurations.
type Options struct {
CAFile string
// If either CertFile or KeyFile is empty, Client() will not load them
// preventing the client from authenticating to the server.
// However, Server() requires them and will error out if they are empty.
CertFile string
KeyFile string
// client-only option
InsecureSkipVerify bool
// server-only option
ClientAuth tls.ClientAuthType
// If ExclusiveRootPools is set, then if a CA file is provided, the root pool used for TLS
// creds will include exclusively the roots in that CA file. If no CA file is provided,
// the system pool will be used.
ExclusiveRootPools bool
MinVersion uint16
// If Passphrase is set, it will be used to decrypt a TLS private key
// if the key is encrypted
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,
tls.TLS_RSA_WITH_AES_256_CBC_SHA,
tls.TLS_RSA_WITH_AES_128_CBC_SHA,
}
// 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
// known weak algorithms removed.
var DefaultServerAcceptedCiphers = append(clientCipherSuites, acceptedCBCCiphers...)
// allTLSVersions lists all the TLS versions and is used by the code that validates
// a uint16 value as a TLS version.
var allTLSVersions = map[uint16]struct{}{
tls.VersionSSL30: {},
tls.VersionTLS10: {},
tls.VersionTLS11: {},
tls.VersionTLS12: {},
}
// 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 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, exclusivePool bool) (*x509.CertPool, error) {
// If we should verify the server, we need to load a trusted ca
var (
certPool *x509.CertPool
err error
)
if exclusivePool {
certPool = x509.NewCertPool()
} else {
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)
}
if !certPool.AppendCertsFromPEM(pem) {
return nil, fmt.Errorf("failed to append certificates from PEM file: %q", caFile)
}
logrus.Debugf("Trusting %d certs", len(certPool.Subjects()))
return certPool, nil
}
// isValidMinVersion checks that the input value is a valid tls minimum version
func isValidMinVersion(version uint16) bool {
_, ok := allTLSVersions[version]
return ok
}
// adjustMinVersion sets the MinVersion on `config`, the input configuration.
// It assumes the current MinVersion on the `config` is the lowest allowed.
func adjustMinVersion(options Options, config *tls.Config) error {
if options.MinVersion > 0 {
if !isValidMinVersion(options.MinVersion) {
return fmt.Errorf("Invalid minimum TLS version: %x", options.MinVersion)
}
if options.MinVersion < config.MinVersion {
return fmt.Errorf("Requested minimum TLS version is too low. Should be at-least: %x", config.MinVersion)
}
config.MinVersion = options.MinVersion
}
return nil
}
// IsErrEncryptedKey returns true if the 'err' is an error of incorrect
// password when tryin to decrypt a TLS private key
func IsErrEncryptedKey(err error) bool {
return errors.Cause(err) == x509.IncorrectPasswordError
}
// getPrivateKey returns the private key in 'keyBytes', in PEM-encoded format.
// If the private key is encrypted, 'passphrase' is used to decrypted the
// private key.
func getPrivateKey(keyBytes []byte, passphrase string) ([]byte, error) {
// this section makes some small changes to code from notary/tuf/utils/x509.go
pemBlock, _ := pem.Decode(keyBytes)
if pemBlock == nil {
return nil, fmt.Errorf("no valid private key found")
}
var err error
if x509.IsEncryptedPEMBlock(pemBlock) {
keyBytes, err = x509.DecryptPEMBlock(pemBlock, []byte(passphrase))
if err != nil {
return nil, errors.Wrap(err, "private key is encrypted, but could not decrypt it")
}
keyBytes = pem.EncodeToMemory(&pem.Block{Type: pemBlock.Type, Bytes: keyBytes})
}
return keyBytes, nil
}
// getCert returns a Certificate from the CertFile and KeyFile in 'options',
// if the key is encrypted, the Passphrase in 'options' will be used to
// decrypt it.
func getCert(options Options) ([]tls.Certificate, error) {
if options.CertFile == "" && options.KeyFile == "" {
return nil, nil
}
errMessage := "Could not load X509 key pair"
cert, err := ioutil.ReadFile(options.CertFile)
if err != nil {
return nil, errors.Wrap(err, errMessage)
}
prKeyBytes, err := ioutil.ReadFile(options.KeyFile)
if err != nil {
return nil, errors.Wrap(err, errMessage)
}
prKeyBytes, err = getPrivateKey(prKeyBytes, options.Passphrase)
if err != nil {
return nil, errors.Wrap(err, errMessage)
}
tlsCert, err := tls.X509KeyPair(cert, prKeyBytes)
if err != nil {
return nil, errors.Wrap(err, errMessage)
}
return []tls.Certificate{tlsCert}, nil
}
// Client returns a TLS configuration meant to be used by a client.
func Client(options Options) (*tls.Config, error) {
tlsConfig := ClientDefault()
tlsConfig.InsecureSkipVerify = options.InsecureSkipVerify
if !options.InsecureSkipVerify && options.CAFile != "" {
CAs, err := certPool(options.CAFile, options.ExclusiveRootPools)
if err != nil {
return nil, err
}
tlsConfig.RootCAs = CAs
}
tlsCerts, err := getCert(options)
if err != nil {
return nil, err
}
tlsConfig.Certificates = tlsCerts
if err := adjustMinVersion(options, tlsConfig); err != nil {
return nil, err
}
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.ClientAuth = options.ClientAuth
tlsCert, err := tls.LoadX509KeyPair(options.CertFile, options.KeyFile)
if err != nil {
if os.IsNotExist(err) {
return nil, fmt.Errorf("Could not load X509 key pair (cert: %q, key: %q): %v", options.CertFile, options.KeyFile, err)
}
return nil, fmt.Errorf("Error reading X509 key pair (cert: %q, key: %q): %v. Make sure the key is not encrypted.", options.CertFile, options.KeyFile, err)
}
tlsConfig.Certificates = []tls.Certificate{tlsCert}
if options.ClientAuth >= tls.VerifyClientCertIfGiven && options.CAFile != "" {
CAs, err := certPool(options.CAFile, options.ExclusiveRootPools)
if err != nil {
return nil, err
}
tlsConfig.ClientCAs = CAs
}
if err := adjustMinVersion(options, tlsConfig); err != nil {
return nil, err
}
return tlsConfig, nil
}

View File

@ -0,0 +1,17 @@
// +build go1.5
// 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,
}

View File

@ -0,0 +1,15 @@
// +build !go1.5
// 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_128_GCM_SHA256,
tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
}

View File

@ -0,0 +1,651 @@
package tlsconfig
import (
"bytes"
"crypto/tls"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"os"
"reflect"
"testing"
)
// This is the currently active LetsEncrypt IdenTrust cross-signed CA cert. It expires Mar 17, 2021.
const (
systemRootTrustedCert = `
-----BEGIN CERTIFICATE-----
MIIEkjCCA3qgAwIBAgIQCgFBQgAAAVOFc2oLheynCDANBgkqhkiG9w0BAQsFADA/
MSQwIgYDVQQKExtEaWdpdGFsIFNpZ25hdHVyZSBUcnVzdCBDby4xFzAVBgNVBAMT
DkRTVCBSb290IENBIFgzMB4XDTE2MDMxNzE2NDA0NloXDTIxMDMxNzE2NDA0Nlow
SjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUxldCdzIEVuY3J5cHQxIzAhBgNVBAMT
GkxldCdzIEVuY3J5cHQgQXV0aG9yaXR5IFgzMIIBIjANBgkqhkiG9w0BAQEFAAOC
AQ8AMIIBCgKCAQEAnNMM8FrlLke3cl03g7NoYzDq1zUmGSXhvb418XCSL7e4S0EF
q6meNQhY7LEqxGiHC6PjdeTm86dicbp5gWAf15Gan/PQeGdxyGkOlZHP/uaZ6WA8
SMx+yk13EiSdRxta67nsHjcAHJyse6cF6s5K671B5TaYucv9bTyWaN8jKkKQDIZ0
Z8h/pZq4UmEUEz9l6YKHy9v6Dlb2honzhT+Xhq+w3Brvaw2VFn3EK6BlspkENnWA
a6xK8xuQSXgvopZPKiAlKQTGdMDQMc2PMTiVFrqoM7hD8bEfwzB/onkxEz0tNvjj
/PIzark5McWvxI0NHWQWM6r6hCm21AvA2H3DkwIDAQABo4IBfTCCAXkwEgYDVR0T
AQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwfwYIKwYBBQUHAQEEczBxMDIG
CCsGAQUFBzABhiZodHRwOi8vaXNyZy50cnVzdGlkLm9jc3AuaWRlbnRydXN0LmNv
bTA7BggrBgEFBQcwAoYvaHR0cDovL2FwcHMuaWRlbnRydXN0LmNvbS9yb290cy9k
c3Ryb290Y2F4My5wN2MwHwYDVR0jBBgwFoAUxKexpHsscfrb4UuQdf/EFWCFiRAw
VAYDVR0gBE0wSzAIBgZngQwBAgEwPwYLKwYBBAGC3xMBAQEwMDAuBggrBgEFBQcC
ARYiaHR0cDovL2Nwcy5yb290LXgxLmxldHNlbmNyeXB0Lm9yZzA8BgNVHR8ENTAz
MDGgL6AthitodHRwOi8vY3JsLmlkZW50cnVzdC5jb20vRFNUUk9PVENBWDNDUkwu
Y3JsMB0GA1UdDgQWBBSoSmpjBH3duubRObemRWXv86jsoTANBgkqhkiG9w0BAQsF
AAOCAQEA3TPXEfNjWDjdGBX7CVW+dla5cEilaUcne8IkCJLxWh9KEik3JHRRHGJo
uM2VcGfl96S8TihRzZvoroed6ti6WqEBmtzw3Wodatg+VyOeph4EYpr/1wXKtx8/
wApIvJSwtmVi4MFU5aMqrSDE6ea73Mj2tcMyo5jMd6jmeWUHK8so/joWUoHOUgwu
X4Po1QYz+3dszkDqMp4fklxBwXRsW10KXzPMTZ+sOPAveyxindmjkW8lGy+QsRlG
PfZ+G6Z6h7mjem0Y+iWlkYcV4PIWL1iwBi8saCbGS5jN2p8M+X+Q7UNKEkROb3N6
KOqkqm57TH2H3eDJAkSnh6/DNFu0Qg==
-----END CERTIFICATE-----
`
rsaPrivateKeyFile = "fixtures/key.pem"
certificateFile = "fixtures/cert.pem"
multiCertificateFile = "fixtures/multi.pem"
rsaEncryptedPrivateKeyFile = "fixtures/encrypted_key.pem"
certificateOfEncryptedKeyFile = "fixtures/cert_of_encrypted_key.pem"
)
// returns the name of a pre-generated, multiple-certificate CA file
// with both RSA and ECDSA certs.
func getMultiCert() string {
return multiCertificateFile
}
// returns the names of pre-generated key and certificate files.
func getCertAndKey() (string, string) {
return rsaPrivateKeyFile, certificateFile
}
// returns the names of pre-generated, encrypted private key and
// corresponding certificate file
func getCertAndEncryptedKey() (string, string) {
return rsaEncryptedPrivateKeyFile, certificateOfEncryptedKeyFile
}
// If the cert files and directory are provided but are invalid, an error is
// returned.
func TestConfigServerTLSFailsIfUnableToLoadCerts(t *testing.T) {
key, cert := getCertAndKey()
ca := getMultiCert()
tempFile, err := ioutil.TempFile("", "cert-test")
if err != nil {
t.Fatal("Unable to create temporary empty file")
}
defer os.RemoveAll(tempFile.Name())
tempFile.Close()
for _, badFile := range []string{"not-a-file", tempFile.Name()} {
for i := 0; i < 3; i++ {
files := []string{cert, key, ca}
files[i] = badFile
result, err := Server(Options{
CertFile: files[0],
KeyFile: files[1],
CAFile: files[2],
ClientAuth: tls.VerifyClientCertIfGiven,
})
if err == nil || result != nil {
t.Fatal("Expected a non-real file to error and return a nil TLS config")
}
}
}
}
// If server cert and key are provided and client auth and client CA are not
// set, a tls config with only the server certs will be returned.
func TestConfigServerTLSServerCertsOnly(t *testing.T) {
key, cert := getCertAndKey()
keypair, err := tls.LoadX509KeyPair(cert, key)
if err != nil {
t.Fatal("Unable to load the generated cert and key")
}
tlsConfig, err := Server(Options{
CertFile: cert,
KeyFile: key,
})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure server TLS", err)
}
if len(tlsConfig.Certificates) != 1 {
t.Fatal("Unexpected server certificates")
}
if len(tlsConfig.Certificates[0].Certificate) != len(keypair.Certificate) {
t.Fatal("Unexpected server certificates")
}
for i, cert := range tlsConfig.Certificates[0].Certificate {
if !bytes.Equal(cert, keypair.Certificate[i]) {
t.Fatal("Unexpected server certificates")
}
}
if !reflect.DeepEqual(tlsConfig.CipherSuites, DefaultServerAcceptedCiphers) {
t.Fatal("Unexpected server cipher suites")
}
if !tlsConfig.PreferServerCipherSuites {
t.Fatal("Expected server to prefer cipher suites")
}
if tlsConfig.MinVersion != tls.VersionTLS10 {
t.Fatal("Unexpected server TLS version")
}
}
// If client CA is provided, it will only be used if the client auth is >=
// VerifyClientCertIfGiven
func TestConfigServerTLSClientCANotSetIfClientAuthTooLow(t *testing.T) {
key, cert := getCertAndKey()
ca := getMultiCert()
tlsConfig, err := Server(Options{
CertFile: cert,
KeyFile: key,
ClientAuth: tls.RequestClientCert,
CAFile: ca,
})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure server TLS", err)
}
if len(tlsConfig.Certificates) != 1 {
t.Fatal("Unexpected server certificates")
}
if tlsConfig.ClientAuth != tls.RequestClientCert {
t.Fatal("ClientAuth was not set to what was in the options")
}
if tlsConfig.ClientCAs != nil {
t.Fatalf("Client CAs should never have been set")
}
}
// If client CA is provided, it will only be used if the client auth is >=
// VerifyClientCertIfGiven
func TestConfigServerTLSClientCASet(t *testing.T) {
key, cert := getCertAndKey()
ca := getMultiCert()
tlsConfig, err := Server(Options{
CertFile: cert,
KeyFile: key,
ClientAuth: tls.VerifyClientCertIfGiven,
CAFile: ca,
})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure server TLS", err)
}
if len(tlsConfig.Certificates) != 1 {
t.Fatal("Unexpected server certificates")
}
if tlsConfig.ClientAuth != tls.VerifyClientCertIfGiven {
t.Fatal("ClientAuth was not set to what was in the options")
}
basePool, err := SystemCertPool()
if err != nil {
basePool = x509.NewCertPool()
}
// because we are not enabling `ExclusiveRootPools`, any root pool will also contain the system roots
if tlsConfig.ClientCAs == nil || len(tlsConfig.ClientCAs.Subjects()) != len(basePool.Subjects())+2 {
t.Fatalf("Client CAs were never set correctly")
}
}
// Exclusive root pools determines whether the CA pool will be a union of the system
// certificate pool and custom certs, or an exclusive or of the custom certs and system pool
func TestConfigServerExclusiveRootPools(t *testing.T) {
key, cert := getCertAndKey()
ca := getMultiCert()
caBytes, err := ioutil.ReadFile(ca)
if err != nil {
t.Fatal("Unable to read CA certs", err)
}
var testCerts []*x509.Certificate
for _, pemBytes := range [][]byte{caBytes, []byte(systemRootTrustedCert)} {
pemBlock, _ := pem.Decode(pemBytes)
if pemBlock == nil {
t.Fatal("Malformed certificate")
}
cert, err := x509.ParseCertificate(pemBlock.Bytes)
if err != nil {
t.Fatal("Unable to parse certificate")
}
testCerts = append(testCerts, cert)
}
// ExclusiveRootPools not set, so should be able to verify both system-signed certs
// and custom CA-signed certs
tlsConfig, err := Server(Options{
CertFile: cert,
KeyFile: key,
ClientAuth: tls.VerifyClientCertIfGiven,
CAFile: ca,
})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure server TLS", err)
}
for i, cert := range testCerts {
if _, err := cert.Verify(x509.VerifyOptions{Roots: tlsConfig.ClientCAs}); err != nil {
t.Fatalf("Unable to verify certificate %d: %v", i, err)
}
}
// ExclusiveRootPools set and custom CA provided, so system certs should not be verifiable
// and custom CA-signed certs should be verifiable
tlsConfig, err = Server(Options{
CertFile: cert,
KeyFile: key,
ClientAuth: tls.VerifyClientCertIfGiven,
CAFile: ca,
ExclusiveRootPools: true,
})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure server TLS", err)
}
for i, cert := range testCerts {
_, err := cert.Verify(x509.VerifyOptions{Roots: tlsConfig.ClientCAs})
switch {
case i == 0 && err != nil:
t.Fatal("Unable to verify custom certificate, even though the root pool should have only the custom CA", err)
case i == 1 && err == nil:
t.Fatal("Successfully verified system root-signed certificate though the root pool should have only the cusotm CA", err)
}
}
// No CA file provided, system cert should be verifiable only
tlsConfig, err = Server(Options{
CertFile: cert,
KeyFile: key,
})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure server TLS", err)
}
for i, cert := range testCerts {
_, err := cert.Verify(x509.VerifyOptions{Roots: tlsConfig.ClientCAs})
switch {
case i == 1 && err != nil:
t.Fatal("Unable to verify system root-signed certificate, even though the root pool should be the system pool only", err)
case i == 0 && err == nil:
t.Fatal("Successfully verified custom certificate though the root pool should be the system pool only", err)
}
}
}
// If a valid minimum version is specified in the options, the server's
// minimum version should be set accordingly
func TestConfigServerTLSMinVersionIsSetBasedOnOptions(t *testing.T) {
versions := []uint16{
tls.VersionTLS11,
tls.VersionTLS12,
}
key, cert := getCertAndKey()
for _, v := range versions {
tlsConfig, err := Server(Options{
MinVersion: v,
CertFile: cert,
KeyFile: key,
})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure server TLS", err)
}
if tlsConfig.MinVersion != v {
t.Fatal("Unexpected minimum TLS version: ", tlsConfig.MinVersion)
}
}
}
// An error should be returned if the specified minimum version for the server
// is too low, i.e. less than VersionTLS10
func TestConfigServerTLSMinVersionNotSetIfMinVersionIsTooLow(t *testing.T) {
key, cert := getCertAndKey()
_, err := Server(Options{
MinVersion: tls.VersionSSL30,
CertFile: cert,
KeyFile: key,
})
if err == nil {
t.Fatal("Should have returned an error for minimum version below TLS10")
}
}
// An error should be returned if an invalid minimum version for the server is
// in the options struct
func TestConfigServerTLSMinVersionNotSetIfMinVersionIsInvalid(t *testing.T) {
key, cert := getCertAndKey()
_, err := Server(Options{
MinVersion: 1,
CertFile: cert,
KeyFile: key,
})
if err == nil {
t.Fatal("Should have returned error on invalid minimum version option")
}
}
// The root CA is never set if InsecureSkipBoolean is set to true, but the
// default client options are set
func TestConfigClientTLSNoVerify(t *testing.T) {
ca := getMultiCert()
tlsConfig, err := Client(Options{CAFile: ca, InsecureSkipVerify: true})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure client TLS", err)
}
if tlsConfig.RootCAs != nil {
t.Fatal("Should not have set Root CAs", err)
}
if !reflect.DeepEqual(tlsConfig.CipherSuites, clientCipherSuites) {
t.Fatal("Unexpected client cipher suites")
}
if tlsConfig.MinVersion != tls.VersionTLS12 {
t.Fatal("Unexpected client TLS version")
}
if tlsConfig.Certificates != nil {
t.Fatal("Somehow client certificates were set")
}
}
// The root CA is never set if InsecureSkipBoolean is set to false and root CA
// is not provided.
func TestConfigClientTLSNoRoot(t *testing.T) {
tlsConfig, err := Client(Options{})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure client TLS", err)
}
if tlsConfig.RootCAs != nil {
t.Fatal("Should not have set Root CAs", err)
}
if !reflect.DeepEqual(tlsConfig.CipherSuites, clientCipherSuites) {
t.Fatal("Unexpected client cipher suites")
}
if tlsConfig.MinVersion != tls.VersionTLS12 {
t.Fatal("Unexpected client TLS version")
}
if tlsConfig.Certificates != nil {
t.Fatal("Somehow client certificates were set")
}
}
// The RootCA is set if the file is provided and InsecureSkipVerify is false
func TestConfigClientTLSRootCAFileWithOneCert(t *testing.T) {
ca := getMultiCert()
tlsConfig, err := Client(Options{CAFile: ca})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure client TLS", err)
}
basePool, err := SystemCertPool()
if err != nil {
basePool = x509.NewCertPool()
}
// because we are not enabling `ExclusiveRootPools`, any root pool will also contain the system roots
if tlsConfig.RootCAs == nil || len(tlsConfig.RootCAs.Subjects()) != len(basePool.Subjects())+2 {
t.Fatal("Root CAs not set properly", err)
}
if tlsConfig.Certificates != nil {
t.Fatal("Somehow client certificates were set")
}
}
// An error is returned if a root CA is provided but the file doesn't exist.
func TestConfigClientTLSNonexistentRootCAFile(t *testing.T) {
tlsConfig, err := Client(Options{CAFile: "nonexistent"})
if err == nil || tlsConfig != nil {
t.Fatal("Should not have been able to configure client TLS", err)
}
}
// An error is returned if either the client cert or the key are provided
// but invalid or blank.
func TestConfigClientTLSClientCertOrKeyInvalid(t *testing.T) {
key, cert := getCertAndKey()
tempFile, err := ioutil.TempFile("", "cert-test")
if err != nil {
t.Fatal("Unable to create temporary empty file")
}
defer os.Remove(tempFile.Name())
tempFile.Close()
for i := 0; i < 2; i++ {
for _, invalid := range []string{"not-a-file", "", tempFile.Name()} {
files := []string{cert, key}
files[i] = invalid
tlsConfig, err := Client(Options{CertFile: files[0], KeyFile: files[1]})
if err == nil || tlsConfig != nil {
t.Fatal("Should not have been able to configure client TLS", err)
}
}
}
}
// The certificate is set if the client cert and client key are provided and
// valid.
func TestConfigClientTLSValidClientCertAndKey(t *testing.T) {
key, cert := getCertAndKey()
keypair, err := tls.LoadX509KeyPair(cert, key)
if err != nil {
t.Fatal("Unable to load the generated cert and key")
}
tlsConfig, err := Client(Options{CertFile: cert, KeyFile: key})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure client TLS", err)
}
if len(tlsConfig.Certificates) != 1 {
t.Fatal("Unexpected client certificates")
}
if len(tlsConfig.Certificates[0].Certificate) != len(keypair.Certificate) {
t.Fatal("Unexpected client certificates")
}
for i, cert := range tlsConfig.Certificates[0].Certificate {
if !bytes.Equal(cert, keypair.Certificate[i]) {
t.Fatal("Unexpected client certificates")
}
}
if tlsConfig.RootCAs != nil {
t.Fatal("Root CAs should not have been set", err)
}
}
// The certificate is set if the client cert and encrypted client key are
// provided and valid and passphrase can decrypt the key
func TestConfigClientTLSValidClientCertAndEncryptedKey(t *testing.T) {
key, cert := getCertAndEncryptedKey()
tlsConfig, err := Client(Options{
CertFile: cert,
KeyFile: key,
Passphrase: "FooBar123",
})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure client TLS", err)
}
if len(tlsConfig.Certificates) != 1 {
t.Fatal("Unexpected client certificates")
}
}
// The certificate is not set if the provided passphrase cannot decrypt
// the encrypted key.
func TestConfigClientTLSNotSetWithInvalidPassphrase(t *testing.T) {
key, cert := getCertAndEncryptedKey()
tlsConfig, err := Client(Options{
CertFile: cert,
KeyFile: key,
Passphrase: "InvalidPassphrase",
})
if !IsErrEncryptedKey(err) || tlsConfig != nil {
t.Fatal("Expected failure due to incorrect passphrase.")
}
}
// Exclusive root pools determines whether the CA pool will be a union of the system
// certificate pool and custom certs, or an exclusive or of the custom certs and system pool
func TestConfigClientExclusiveRootPools(t *testing.T) {
ca := getMultiCert()
caBytes, err := ioutil.ReadFile(ca)
if err != nil {
t.Fatal("Unable to read CA certs", err)
}
var testCerts []*x509.Certificate
for _, pemBytes := range [][]byte{caBytes, []byte(systemRootTrustedCert)} {
pemBlock, _ := pem.Decode(pemBytes)
if pemBlock == nil {
t.Fatal("Malformed certificate")
}
cert, err := x509.ParseCertificate(pemBlock.Bytes)
if err != nil {
t.Fatal("Unable to parse certificate")
}
testCerts = append(testCerts, cert)
}
// ExclusiveRootPools not set, so should be able to verify both system-signed certs
// and custom CA-signed certs
tlsConfig, err := Client(Options{CAFile: ca})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure client TLS", err)
}
for i, cert := range testCerts {
if _, err := cert.Verify(x509.VerifyOptions{Roots: tlsConfig.RootCAs}); err != nil {
t.Fatalf("Unable to verify certificate %d: %v", i, err)
}
}
// ExclusiveRootPools set and custom CA provided, so system certs should not be verifiable
// and custom CA-signed certs should be verifiable
tlsConfig, err = Client(Options{
CAFile: ca,
ExclusiveRootPools: true,
})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure client TLS", err)
}
for i, cert := range testCerts {
_, err := cert.Verify(x509.VerifyOptions{Roots: tlsConfig.RootCAs})
switch {
case i == 0 && err != nil:
t.Fatal("Unable to verify custom certificate, even though the root pool should have only the custom CA", err)
case i == 1 && err == nil:
t.Fatal("Successfully verified system root-signed certificate though the root pool should have only the cusotm CA", err)
}
}
// No CA file provided, system cert should be verifiable only
tlsConfig, err = Client(Options{})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure client TLS", err)
}
for i, cert := range testCerts {
_, err := cert.Verify(x509.VerifyOptions{Roots: tlsConfig.RootCAs})
switch {
case i == 1 && err != nil:
t.Fatal("Unable to verify system root-signed certificate, even though the root pool should be the system pool only", err)
case i == 0 && err == nil:
t.Fatal("Successfully verified custom certificate though the root pool should be the system pool only", err)
}
}
}
// If a valid MinVersion is specified in the options, the client's
// minimum version should be set accordingly
func TestConfigClientTLSMinVersionIsSetBasedOnOptions(t *testing.T) {
key, cert := getCertAndKey()
tlsConfig, err := Client(Options{
MinVersion: tls.VersionTLS12,
CertFile: cert,
KeyFile: key,
})
if err != nil || tlsConfig == nil {
t.Fatal("Unable to configure client TLS", err)
}
if tlsConfig.MinVersion != tls.VersionTLS12 {
t.Fatal("Unexpected minimum TLS version: ", tlsConfig.MinVersion)
}
}
// An error should be returned if the specified minimum version for the client
// is too low, i.e. less than VersionTLS12
func TestConfigClientTLSMinVersionNotSetIfMinVersionIsTooLow(t *testing.T) {
key, cert := getCertAndKey()
_, err := Client(Options{
MinVersion: tls.VersionTLS11,
CertFile: cert,
KeyFile: key,
})
if err == nil {
t.Fatal("Should have returned an error for minimum version below TLS12")
}
}
// An error should be returned if an invalid minimum version for the client is
// in the options struct
func TestConfigClientTLSMinVersionNotSetIfMinVersionIsInvalid(t *testing.T) {
key, cert := getCertAndKey()
_, err := Client(Options{
MinVersion: 1,
CertFile: cert,
KeyFile: key,
})
if err == nil {
t.Fatal("Should have returned error on invalid minimum version option")
}
}

View File

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC1jCCAb6gAwIBAgIDAw0/MA0GCSqGSIb3DQEBCwUAMA8xDTALBgNVBAMTBHRl
c3QwHhcNMTYwMzI4MTg0MTQ3WhcNMjcwMzI4MTg0MTQ3WjAPMQ0wCwYDVQQDEwR0
ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA1k1NO4wzCpxZ71Bo
SiYSWh8SE9jHtg6lz0QjMQXzFuLhpedjHJYx9fYbD+JVk5vnRbUqNUeZVKAGahfR
9vhm5I+cm359gYU0gHawLw91oh4JCiwUu77U2obHvtvcXLf6Fb/+MoSA5wH7vbL3
T4vR1+hLt+R+kILAEHq/IlSdLD8CA0iA+ypHfCPOi5F2wVjAyMnQXgVDkAhzefpu
JkhN1yUgb5WK4qoSuOUDUYq/bRosLdHXDJiWRuqaU2zxO5cHVlrNAE5RuspfEzl4
YP6boZTOomLEDbBTSJWgX2/ybvY7o4sCw7KrvyBIqSK9HbfaK1nFMFGoiSH6+1m4
amWKrwIDAQABozswOTAOBgNVHQ8BAf8EBAMCBaAwGQYDVR0lBBIwEAYIKwYBBQUH
AwMGBFUdJQAwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEADuXjLtGk
tU5ql+LFB32Cc2Laa0iO8aqJccOcXYKg4FD0um+1+YQO1CBZZqWjItH4CuJl5+2j
Tc9sFgrIVH5CmvUkOUFPCNDAJtxBvF6RQqRpehjheHDaNsYo9JNKHKEJB6OJrDgy
N5krM5FKyAp/EDTbIrGIZFMdxQGxK5MfpfPkKK44JgOQM3QWeR+LqIpfd34MD1jZ
jjYdl0+quIHiIdFR0a4Uam7o9GfUmcWe1VFthLb5pNhV6t+wyuLyMXVMNacKZSz/
nOMWVQfgViZk6rHOPSMrFMc7Pp488I907MJKCryd21LcLqMuhb4BpWcJghnY8Lbs
uIPLsUHr3Pfp9Q==
-----END CERTIFICATE-----

View File

@ -0,0 +1,18 @@
-----BEGIN CERTIFICATE-----
MIIC1jCCAb6gAwIBAgIDAw0/MA0GCSqGSIb3DQEBCwUAMA8xDTALBgNVBAMTBHRl
c3QwHhcNMTYwNDIyMDQyMjM1WhcNMTgwNDIyMDQyMjM1WjAPMQ0wCwYDVQQDEwR0
ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4GRTos+Ik6kQG7wn
8E4HqPwgWXbY0T59UQsrbR+YbyxbUKV67Pgl4VImuUmYaism6Tm3EFYzeom5baMc
vW0hC+WbwVr1rq5ddBE8akYhlPY40SxFlh563vOi7lcFGM7xuUbTlhtAhYa5xc5U
thHYa8Mdqc2kMrmU4JBhNHoRk2mnRBo2J2/8RfOfioM6mH0t/MVtB/jSGpcwbbfj
2twKOpB9CoX57szVo7+DCFHpLxeuop+69REu5Egc2a5BtBuUf0fkUBKuF7yUy2xI
IbgjCiGb3Z+PCIC0CjNt9wExowPAGfxAJ8s1nNlpZav3707VZRtz7Js1skRjm9aU
8fhYNQIDAQABozswOTAOBgNVHQ8BAf8EBAMCBaAwGQYDVR0lBBIwEAYIKwYBBQUH
AwMGBFUdJQAwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAcKCCV5Os
O2U7Ekp0jzOusV2+ykZzUe4sEds+ikblxK9SHV/pAPIVuAevdyE1LKmJ6ZGgeU2M
4MC6jC/XTlYNhsYCfKaJn53UscKI2urXFlk1Gv5VQP5EOrMWb76A5uj1nElxKe2C
bMVoUuMwRd9jnz6594D80jGGYpHRaF7yLtGbiflDjB+yv1OU6WnuVNr0nOb9ShR6
WPlrQj5TUSpRHF/oKy9LVWuxYA9aiY1YREDZhhauw9pGAMx1lImfJcJ077MdxN4A
DwKAx3ooajAu1n3McY1oncWW+rWs2Ptvp6lKMGoZ50ElEPCMw4/hPtPMLq/DTWNj
l342KLVWgchlIA==
-----END CERTIFICATE-----

View File

@ -0,0 +1,30 @@
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-256-CBC,68ce1d54f187b663e152d9c5dc900fd3
ZVBeXx7kWiF0yPOORntrN6BsyIJE7krqTVhRfk6GAllaLQv0jvb31XHB1oWOaqnx
tb7kUuoBeQdl1hs/iAnkDMc59WJfEK9A9cAD/SgxTgdENOrzFSRNEfqketLA4eHZ
2sOLkSfv58HwA0p0gzqSrLQBo/6ZtF/57HxH166PtErPNTS1Usu/f4Oj0UqxTfbZ
B5LHsepyNLt6q/15fcY0TFYJwvgEXa4SridjT+8bTz2T+bx3QFijGnl7EdkTElni
FIwnDjFZaAULqoyUIB1y8guEZVkaWKncxPdRfhId84HklWdrrLtP5D6db1xNNpsp
LzGdciD3phJp6K0hpl+WrhYxuCKURa27tXMCuYOFd1hw/kM29jFbxSIlNBGN4OLL
v4wYrJFM21iWsz9c7Cqw5Yls2Rsx0QrXRFIxwT25z+HNx1fysQxYuxf3r+e2oz8e
8Os7hvcxG2XDz01/zpx8kzxUcLuh+3o5UOYlo9z6qsjaD5NUXY+X90PUrVO9fk5y
8o8pnElPnV88Ihrog5YTYy6egiQWHhDk2I4qlYPOBQNKTLg3KulAcmC9vQ8mR5Sy
p3c3MTgh0A3Zk5Dib+sQ0tdbwDcB2JCTqGal1FNEW5Z7qTHA4Bdm2l7hGs8cRpy4
Ehkhv3s5wWmKcbwwlPuJ0UfPeDn6v9qE2/IkOy+jWgTpaFyWtXHc1/XdqMsJ8xN0
thJw/GMtNabB1+zuayJnvmbJd2qW1smsFTHqX3BovXIH4vx1hE2d0lJpEBynk+wr
gpPgrRoEiqsPcsRoVjvKH3qwJLRdcGYhKqhbvRdynlagCLmE8iAI99r82u6t+03h
YNpRbafY4ceAYyK0IlRiJvGkBMfH7bMXcBMmXyQSBF27ZpNidyZSCHrU5xyHqJZO
XWUhl9GHplBfueh5E831S7mDqobd8RqnUvKVygyEOol5VUFDrggTAAKKN9VzM3uT
MaVymt6fA7stzf01fT+Wi7uCm5legTXG3Ca+XxD6TdE0dNzewd5jDsuqwXnt1iC4
slvuLRZeRZDNvBd0G7Ohhp6jb2HHwkv9kQTZ+UEDbR/Gwxty4oT1MnwSE0mi9ZFN
6PTjrSxpIKe+mAhgzrepLMfATGayYQzucEArPG7Vp+NJva+j6FKloqrzXMjlP0hN
XSBr7AL+j+OR/tzDOoUG3xdsCl/u5hFTpjsW2ti870zoRUcK0fqJ9UIYjh66L7yT
KNkXsC+OcGuGkhtQ0gxx60OI7wp4bh2pKdT6e111/WTvXxVR2C3XhFBLUfNIz/7A
Oj+s0CaV4pBmCjIobLYpxC0ofLplwBLGf9xnsBiQF5dsgKgOhACeDmDMwqAJ3U/t
54hK/8Yb8W46Tjgbm0Qsj5gFXHofnyqDeQxAjsdCXsdMaPB8nyZpEkuQSEj9HlKW
xIEErVufkvqyrzhX1pxPs+C839Ueyeob6ZWQurqCLTdZh+3bhKcvi5iP+aLLjMWK
JT9tmAuFVkbPerqObVQFbnM4/re33YYD7QXCqta5bxcVeBI8N1HdwMYrDVhXelEx
mqGleUkkDHTWzAa3u1GKOzLXAYnD0TsTwml0+k+Rf0QMBiDJiKujfy7fGqfZF2vR
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpQIBAAKCAQEA1k1NO4wzCpxZ71BoSiYSWh8SE9jHtg6lz0QjMQXzFuLhpedj
HJYx9fYbD+JVk5vnRbUqNUeZVKAGahfR9vhm5I+cm359gYU0gHawLw91oh4JCiwU
u77U2obHvtvcXLf6Fb/+MoSA5wH7vbL3T4vR1+hLt+R+kILAEHq/IlSdLD8CA0iA
+ypHfCPOi5F2wVjAyMnQXgVDkAhzefpuJkhN1yUgb5WK4qoSuOUDUYq/bRosLdHX
DJiWRuqaU2zxO5cHVlrNAE5RuspfEzl4YP6boZTOomLEDbBTSJWgX2/ybvY7o4sC
w7KrvyBIqSK9HbfaK1nFMFGoiSH6+1m4amWKrwIDAQABAoIBAQC802wj9grbZJzS
A1WBUD6Hbi0tk6uVPR7YnD8t6QIivlL5LgLko2ruQKXjvxiMcai8gT7pp2bxa/d6
7/Yv2PxAlFH3qOLJhyeVsf7X2JVb/X8VmXXDYAiJbI0AHRX0FJ+lHoDK3nn+En9Q
zSqgyqBhz+s343uptauqWZ2kkE3VNyqlPBhmKc5NcbR7Sgb4nJ3CkNAcxRkl1NeI
BRFdsTUYRNR3Vd++OvOzI4uzZfCIeUVqx+r7/SeLW0UwqeprMm7g+hFQLfH+e9SA
9lx0EIRoQFwgvKju2eogpSwvkSlObXnESu5OHYtnc+jpsOC0EbQgO0d6CqVZiqjR
2dRYsZkhAoGBAO69loXSAsyqUj0rT5iq59PuMlBEAlW6hQTfl6c8bnu1JUo2s/CH
OJfswxfHN32qmi99WbK2iLyrnznNYsyPnYKW0ObwuoqAdrlydfu7Fq9HSOACoIvK
jRMOsiJtM3JX2bHHV7yIwJ1+h++o2Ly803j7tKtYsrRQVZiWeTcR2IRZAoGBAOXL
bJFLbAhm3zRqhbiWuORqqyLxrDmIB6RY8vTdX47vwzkFGZJlCuL+vs6877I6eOc9
wjH9qcOiJQJ4DWkAE+VS5PAPoj0UDRw7AkE9v3RwnmxvAfP5rPo5KimYxKq4yX6r
+Qc4ixwftCj0rxFoG4lnipwBFq4NXuHtIhbZXMZHAoGBAOGfatGtV9f0XyRP+jld
yxoO0p3oqAw86dlhNgFmq0NePo+UgxmdsW5i4z1lmJu6z1xyKoMq3q7vwtrtr6GD
WGhB/8tBVgnuvkUkVzw/44Bi7gxGb1OtaQXJra+7ZBN70tCgg9o5o080dWOZPruf
+Hst5eDJQpoGEd7S1lulEeqBAoGBAKAqdIak6izE/wg6wu+Q5lgW3SejCOakoKb1
dIoljkhDZ2/j1RoLoVXsNzRDzlIMnV6X1jYf1ubLqj4ZTUeFTVjGuVl1nCA0TJsD
qiOtFTfkkxeDG/pgaSeTFocdut4/o/nNhep5h8RXeKwfN7LLPH4+FAd+Xr98BEk2
jk8cu6RbAoGAHI9yRXKjlADBZLvxxMGHRfe7eK4PgABmluZLdsXzNmXxybrZDvdC
teipvIUSym7tvdDB6LHXKVp4mYeqHe/ktRatlhbQyPso2VPoMFQyuRBYKKFFAh0V
3d6EyTRnIxn/NW+XdcCUeufFfd+3BHyux68PyUsTtKRCJYfhExzJf70=
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,28 @@
-----BEGIN CERTIFICATE-----
MIIC3DCCAcSgAwIBAgIDAw0/MA0GCSqGSIb3DQEBCwUAMA8xDTALBgNVBAMTBHRl
c3QwHhcNMTYwMzI4MTg0MTQ3WhcNMjcwMzI4MTg0MTQ3WjAPMQ0wCwYDVQQDEwR0
ZXN0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArVIJDnNnM1iX7Xj8
bja4WsgHuENRBsBCROTDjQL1w7Ksin2jmCl/D7Gk9ifRJZ/HPE3BKo6B+3CDXygJ
Qvoe8SGWi6ae8lN4VgPoW7xDViAWhVmjIr+dNQXWD0hCq0YZuXyYSi5iXWeRaTvx
2eoG2VSkNnkc/0weEhX1nBGBscuz1UZqWp53m09eL7otngcNcdjmvLPiw4E3cric
UoLVonzf4ZE84Q7nNmfWfMKh4zJUyn8N766GAAoC6RAKsJ0xSDeRjkzSy7vGJKBv
nTBe6X1xyFZaN0mAjtRkYaxI9ZfI8K41Trhd88s4B4G61p70DY3dMLmuF8wGHVCF
lMMV6wIDAQABo0EwPzAOBgNVHQ8BAf8EBAMCAqQwGQYDVR0lBBIwEAYIKwYBBQUH
AwMGBFUdJQAwEgYDVR0TAQH/BAgwBgEB/wIBATANBgkqhkiG9w0BAQsFAAOCAQEA
LriCH0FTaOFIBl+kxAKjs7puhIZoYLwQ8IReXdEU7kYjPff3X/eiO82A0GwMM9Fp
/RdMlZGDSLyZ1a/gKCz55j9J4MW8ZH7RSEQs3dJQCvEPDO6UdgKy4Ft9yNh/ba1J
8/n0CqR+0QNov6Qp7eMDkQaDvKgCaABn8at6VLtuifJXFKDGt0LrR7wkQBJ85SZB
9GdfNSPzEZkb4FQ2gPgAk7ySoQ6Hi6mogEORbtJ7+Xiq57J+cEZQV6TOuwYgBG4e
MW3h37+7V5a/absybik1F/gcx4IbEBd/7an6a+a2l5FeTED5kpzvD4+yrQAoY8lT
gccRdP0O4CsLn7zlLRidPQ==
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIIBTzCB9qADAgECAgMDDT8wCgYIKoZIzj0EAwIwDzENMAsGA1UEAxMEdGVzdDAe
Fw0xNjAzMjgxODQxNDdaFw0yNzAzMjgxODQxNDdaMA8xDTALBgNVBAMTBHRlc3Qw
WTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQy8xfFkSiJA10EC1MMJzkLgu6csocC
UNyix7zOqijLsASE4an5LQsZ1PuhgVYnL+B9rAcnXgJaLM8YOmLRPqNdo0EwPzAO
BgNVHQ8BAf8EBAMCAqQwGQYDVR0lBBIwEAYIKwYBBQUHAwMGBFUdJQAwEgYDVR0T
AQH/BAgwBgEB/wIBATAKBggqhkjOPQQDAgNIADBFAiEAwUrZY7fHwr4FWONiBJo6
97V9GAbj70ZJqV5M7rt+hMECIFY66kUrv0sG2vlhicSIGwSOdB3VcijdZSelzLn1
iRk5
-----END CERTIFICATE-----