Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,15 @@ language: go

go:
- 1.13
Comment thread
nathanejohnson marked this conversation as resolved.
Outdated
- 1.14
- 1.15
- master

addons:
hosts:
- example.com
- example2.com

script: make travis

jobs:
Expand Down
8 changes: 4 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
FROM golang:1.13.4-alpine AS build
FROM golang:1.14.7-alpine AS build

ARG consul_version=1.6.1
ARG consul_version=1.8.2
ADD https://releases.hashicorp.com/consul/${consul_version}/consul_${consul_version}_linux_amd64.zip /usr/local/bin
RUN cd /usr/local/bin && unzip consul_${consul_version}_linux_amd64.zip

ARG vault_version=1.2.3
ARG vault_version=1.5.0
ADD https://releases.hashicorp.com/vault/${vault_version}/vault_${vault_version}_linux_amd64.zip /usr/local/bin
RUN cd /usr/local/bin && unzip vault_${vault_version}_linux_amd64.zip

Expand All @@ -13,7 +13,7 @@ COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go test -mod=vendor -trimpath -ldflags "-s -w" ./...
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -mod=vendor -trimpath -ldflags "-s -w"

FROM alpine:3.10
FROM alpine:3.12
RUN apk update && apk add --no-cache ca-certificates
COPY --from=build /src/fabio /usr/bin
ADD fabio.properties /etc/fabio/fabio.properties
Expand Down
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# CUR_TAG is the last git tag plus the delta from the current commit to the tag
# e.g. v1.5.5-<nr of commits since>-g<current git sha>
CUR_TAG ?= $(shell git describe)
CUR_TAG ?= $(shell git describe --tags --first-parent)

# LAST_TAG is the last git tag
# e.g. v1.5.5
LAST_TAG ?= $(shell git describe --abbrev=0)
LAST_TAG ?= $(shell git describe --tags --first-parent --abbrev=0)

# VERSION is the last git tag without the 'v'
# e.g. 1.5.5
VERSION ?= $(shell git describe --abbrev=0 | cut -c 2-)
VERSION ?= $(shell git describe --tags --first-parent --abbrev=0 | cut -c 2-)

# GOFLAGS is the flags for the go compiler.
GOFLAGS ?= -mod=vendor -ldflags "-X main.version=$(CUR_TAG)"
Expand Down
6 changes: 3 additions & 3 deletions config/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ func parseListen(cfg map[string]string, cs map[string]CertSource, readTimeout, w
case "proto":
l.Proto = v
switch l.Proto {
case "tcp", "tcp+sni", "tcp-dynamic", "http", "https", "grpc", "grpcs":
case "tcp", "tcp+sni", "tcp-dynamic", "http", "https", "grpc", "grpcs", "https+tcp+sni":
// ok
default:
return Listen{}, fmt.Errorf("unknown protocol %q", v)
Expand Down Expand Up @@ -461,8 +461,8 @@ func parseListen(cfg map[string]string, cs map[string]CertSource, readTimeout, w
if l.Addr == "" {
return Listen{}, fmt.Errorf("need listening host:port")
}
if csName != "" && l.Proto != "https" && l.Proto != "tcp" && l.Proto != "tcp-dynamic" && l.Proto != "grpcs" {
return Listen{}, fmt.Errorf("cert source requires proto 'https', 'tcp', 'tcp-dynamic' or 'grpcs'")
if csName != "" && l.Proto != "https" && l.Proto != "tcp" && l.Proto != "tcp-dynamic" && l.Proto != "grpcs" && l.Proto != "https+tcp+sni" {
return Listen{}, fmt.Errorf("cert source requires proto 'https', 'tcp', 'tcp-dynamic', 'https+tcp+sni', or 'grpcs'")
}
if csName == "" && l.Proto == "https" {
return Listen{}, fmt.Errorf("proto 'https' requires cert source")
Expand Down
4 changes: 2 additions & 2 deletions config/load_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1058,13 +1058,13 @@ func TestLoad(t *testing.T) {
desc: "-proxy.addr with cert source and proto 'http' requires proto 'https', 'tcp', or 'grpcs'",
args: []string{"-proxy.addr", ":5555;cs=name;proto=http", "-proxy.cs", "cs=name;type=path;cert=value"},
cfg: func(cfg *Config) *Config { return nil },
err: errors.New("cert source requires proto 'https', 'tcp', 'tcp-dynamic' or 'grpcs'"),
err: errors.New("cert source requires proto 'https', 'tcp', 'tcp-dynamic', 'https+tcp+sni', or 'grpcs'"),
},
{
desc: "-proxy.addr with cert source and proto 'tcp+sni' requires proto 'https', 'tcp' or 'grpcs'",
args: []string{"-proxy.addr", ":5555;cs=name;proto=tcp+sni", "-proxy.cs", "cs=name;type=path;cert=value"},
cfg: func(cfg *Config) *Config { return nil },
err: errors.New("cert source requires proto 'https', 'tcp', 'tcp-dynamic' or 'grpcs'"),
err: errors.New("cert source requires proto 'https', 'tcp', 'tcp-dynamic', 'https+tcp+sni', or 'grpcs'"),
},
{
desc: "-proxy.noroutestatus too small",
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ require (
github.com/hashicorp/serf v0.7.0 // indirect
github.com/hashicorp/vault v0.6.0
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect
github.com/inetaf/tcpproxy v0.0.0-20200125044825-b6bb9b5b8252
github.com/jonboulle/clockwork v0.1.0 // indirect
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 // indirect
github.com/kr/pretty v0.1.0 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ github.com/hashicorp/vault v0.6.0 h1:wc/KN2SZ76imak5yOF4Utm6p0e4BNtSKLhWlYrzX+vQ
github.com/hashicorp/vault v0.6.0/go.mod h1:KfSyffbKxoVyspOdlaGVjIuwLobi07qD1bAbosPMpP0=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ=
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
github.com/inetaf/tcpproxy v0.0.0-20200125044825-b6bb9b5b8252 h1:jeqlfkFa5h+Ak/I33QpU4p01nFhw0G5IFm/Rsenne2Y=
github.com/inetaf/tcpproxy v0.0.0-20200125044825-b6bb9b5b8252/go.mod h1:R6mExYS3O0XXjOZye3GtXfbuGF4hWQnF45CFWoj7O6g=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Aowv3l/EQUlocDHW2Cp4G9WJVH7uyH8QFJE=
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
Expand Down
39 changes: 38 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"fmt"
Expand Down Expand Up @@ -49,7 +50,7 @@ import (
// It is also set by the linker when fabio
// is built via the Makefile or the build/docker.sh
// script to ensure the correct version number
var version = "1.5.12"
var version = "1.5.14"

var shuttingDown int32

Expand Down Expand Up @@ -260,6 +261,28 @@ func lookupHostFn(cfg *config.Config) func(string) *route.Target {
}
}

// Returns a matcher function compatible with tcpproxy Matcher from github.com/inetaf/tcpproxy
func lookupHostMatcher(cfg *config.Config) func(context.Context, string) bool {
pick := route.Picker[cfg.Proxy.Strategy]
return func(ctx context.Context, host string) bool {
t := route.GetTable().LookupHost(host, pick)
if t == nil {
return false
}

// Make sure this is supposed to be a tcp proxy.
// opts proto= overrides scheme if present.
var (
ok bool
proto string
)
if proto, ok = t.Opts["proto"]; !ok && t.URL != nil {
proto = t.URL.Scheme
}
return "tcp" == proto
}
}

func makeTLSConfig(l config.Listen) (*tls.Config, error) {
if l.CertSource.Name == "" {
return nil, nil
Expand Down Expand Up @@ -398,6 +421,20 @@ func startServers(cfg *config.Config) {
}
}
}()
case "https+tcp+sni":
go func() {
hp := newHTTPProxy(cfg)
tp := &tcp.SNIProxy{
DialTimeout: cfg.Proxy.DialTimeout,
Lookup: lookupHostFn(cfg),
Conn: metrics.DefaultRegistry.GetCounter("tcp_sni.conn"),
ConnFail: metrics.DefaultRegistry.GetCounter("tcp_sni.connfail"),
Noroute: metrics.DefaultRegistry.GetCounter("tcp_sni.noroute"),
}
if err := proxy.ListenAndServeHTTPSTCPSNI(l, hp, tp, tlscfg, lookupHostMatcher(cfg)); err != nil {
exit.Fatal("[FATAL] ", err)
}
}()
default:
exit.Fatal("[FATAL] Invalid protocol ", l.Proto)
}
Expand Down
11 changes: 11 additions & 0 deletions proxy/http_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -644,6 +644,9 @@ func tlsClientConfig() *tls.Config {
if ok := rootCAs.AppendCertsFromPEM(internal.LocalhostCert); !ok {
panic("could not parse cert")
}
if ok := rootCAs.AppendCertsFromPEM(internal.LocalhostCert2); !ok {
panic("could not parse cert")
}
return &tls.Config{RootCAs: rootCAs}
}

Expand All @@ -655,6 +658,14 @@ func tlsServerConfig() *tls.Config {
return &tls.Config{Certificates: []tls.Certificate{cert}}
}

func tlsServerConfig2() *tls.Config {
cert, err := tls.X509KeyPair(internal.LocalhostCert2, internal.LocalhostKey2)
if err != nil {
panic("failed to set cert")
}
return &tls.Config{Certificates: []tls.Certificate{cert}}
}

func mustParse(rawurl string) *url.URL {
u, err := url.Parse(rawurl)
if err != nil {
Expand Down
104 changes: 104 additions & 0 deletions proxy/inetaf_tcpproxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package proxy

import (
"context"
"errors"
"fmt"
"log"
"net"
"net/http"

"github.com/inetaf/tcpproxy"
)

type childProxy struct {
l net.Listener
s Server
}

type InetAfTCPProxyServer struct {
Proxy *tcpproxy.Proxy
children []*childProxy
}

// Close - implements Server - is this even called?
func (tps *InetAfTCPProxyServer) Close() error {
_ = tps.Proxy.Close()
firstErr := tps.Proxy.Wait()
errChan := make(chan error, len(tps.children))
for _, sl := range tps.children {
go func(sl *childProxy) {
errChan <- sl.s.Close()
}(sl)
}
for range tps.children {
err := <-errChan
if errors.Is(err, http.ErrServerClosed) {
err = nil
}
if firstErr == nil {
firstErr = err
}
if err != nil {
log.Printf("[ERROR] %s", err)
}
}
return firstErr

}

// Serve - implements server. The listener is ignored, but it
// calls serve on the children
func (tps *InetAfTCPProxyServer) Serve(_ net.Listener) error {
if len(tps.children) == 0 {
return fmt.Errorf("no children defined for listener")
}
errChan := make(chan error, len(tps.children))
Comment thread
nathanejohnson marked this conversation as resolved.
Outdated
for _, sl := range tps.children {
go func(sl *childProxy) {
errChan <- sl.s.Serve(sl.l)
}(sl)
}
firstErr := tps.Proxy.Wait()
for range tps.children {
err := <-errChan
if errors.Is(err, http.ErrServerClosed) {
err = nil
}
if firstErr == nil {
firstErr = err
}
if err != nil {
log.Print("[FATAL] ", err)
}
}
return firstErr
}

// ServeLater - l is really only for listeners that are
// tcpproxy.TargetListener or a derivative. Don't call after
// Serve() is called.
func (tps *InetAfTCPProxyServer) ServeLater(l net.Listener, s Server) {
tps.children = append(tps.children, &childProxy{l, s})
}

func (tps *InetAfTCPProxyServer) Shutdown(ctx context.Context) error {
_ = tps.Proxy.Close() // always returns nil error anyway
firstErr := tps.Proxy.Wait() // wait for outer listener to close before telling the childProxy
errChan := make(chan error, len(tps.children))
Comment thread
nathanejohnson marked this conversation as resolved.
Outdated
for _, sl := range tps.children {
go func(sl *childProxy) {
errChan <- sl.s.Shutdown(ctx)
}(sl)
}
for range tps.children {
err := <-errChan
if firstErr == nil {
firstErr = err
}
if err != nil {
log.Print("[ERROR] ", err)
}
}
return firstErr
}
Loading