Skip to content

Commit

Permalink
Add support for http connect proxying to tls connections
Browse files Browse the repository at this point in the history
  • Loading branch information
plorenz committed Aug 15, 2023
1 parent dd9ac23 commit 2c83bc8
Show file tree
Hide file tree
Showing 6 changed files with 265 additions and 19 deletions.
111 changes: 110 additions & 1 deletion address.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,18 @@ import (
"github.com/openziti/identity"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
"golang.org/x/net/proxy"
"io"
"net"
"time"
)

const (
KeyProxy = "proxy"
KeyProtocol = "protocol"
KeyCachedProxyConfiguration = "cachedProxyConfiguration"
)

type Configuration map[interface{}]interface{}

// Protocols returns supported or requested application protocols (used for ALPN support)
Expand All @@ -34,7 +41,7 @@ func (self Configuration) Protocols() []string {
return nil
}

p, found := self["protocol"]
p, found := self[KeyProtocol]
if found {
switch v := p.(type) {
case string:
Expand All @@ -48,6 +55,108 @@ func (self Configuration) Protocols() []string {
return nil
}

func (self Configuration) GetProxyConfiguration() (*ProxyConfiguration, error) {
if val, found := self[KeyCachedProxyConfiguration]; found {
return val.(*ProxyConfiguration), nil
}

result := &ProxyConfiguration{}

val, found := self[KeyProxy]
if !found {
result.Type = ProxyTypeNone
} else {
cfg, ok := val.(map[interface{}]interface{})
if !ok {
return nil, errors.New("invalid proxy configuration value, should be map")
}

var err error
result, err = LoadProxyConfiguration(cfg)
if err != nil {
return nil, err
}
}

self[KeyCachedProxyConfiguration] = result

return result, nil
}

type ProxyType string

const (
ProxyTypeNone ProxyType = "none"
ProxyTypeHttpConnect ProxyType = "http"
ProxyTypeSocks5 ProxyType = "socks5"
)

type ProxyConfiguration struct {
Type ProxyType
Address string
Auth *proxy.Auth
}

func LoadProxyConfiguration(cfg map[interface{}]interface{}) (*ProxyConfiguration, error) {
val, found := cfg["type"]
if !found {
return nil, errors.New("proxy configuration does not specify proxy type")
}

proxyType, ok := val.(string)
if !ok {
return nil, errors.New("proxy type must be a string")
}

if proxyType == string(ProxyTypeNone) {
return &ProxyConfiguration{
Type: ProxyTypeNone,
}, nil
}

result := &ProxyConfiguration{}

switch proxyType {
case string(ProxyTypeHttpConnect):
result.Type = ProxyTypeHttpConnect
case string(ProxyTypeSocks5):
result.Type = ProxyTypeSocks5
default:
return nil, errors.Errorf("invalid proxy type %s", proxyType)
}

val, found = cfg["address"]
if !found {
return nil, errors.Errorf("no address specified for %s proxy", string(result.Type))
}

if addr, ok := val.(string); !ok {
return nil, errors.Errorf("invalid value for %s proxy address [%v], must be string", string(result.Type), val)
} else {
result.Address = addr
}

if val, found = cfg["username"]; found {
if username, ok := val.(string); ok {
result.Auth = &proxy.Auth{
User: username,
}
} else {
return nil, errors.Errorf("invalid value for %s proxy username [%v], must be string", string(result.Type), val)
}

if val, found = cfg["password"]; found {
if password, ok := val.(string); ok {
result.Auth.Password = password
} else {
return nil, errors.Errorf("invalid value for %s proxy password [%v], must be string", string(result.Type), val)
}
}
}

return result, nil
}

// Address implements the functionality provided by a generic "address".
type Address interface {
Dial(name string, i *identity.TokenId, timeout time.Duration, tcfg Configuration) (Conn, error)
Expand Down
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ require (
github.com/emirpasic/gods v1.18.1 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/klauspost/compress v1.10.3 // indirect
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
github.com/miekg/pkcs11 v1.1.1 // indirect
github.com/nxadm/tail v1.4.8 // indirect
Expand All @@ -35,9 +35,9 @@ require (
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/parallaxsecond/parsec-client-go v0.0.0-20220111122524-cb78842db373 // indirect
github.com/parallaxsecond/parsec-client-go v0.0.0-20221025095442-f0a77d263cf9 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/text v0.12.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
12 changes: 7 additions & 5 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -300,8 +300,9 @@ github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hd
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQE9x6ikvDFZS2mDVS3drnohI=
Expand Down Expand Up @@ -353,8 +354,8 @@ github.com/openziti/foundation/v2 v2.0.29 h1:E63p5/esqOJ/OSMePR3fKYHb3Wq2BR4PLkD
github.com/openziti/foundation/v2 v2.0.29/go.mod h1:MpXSCSn4MABvtIXzfTBFqhK5pNsNXHWnR8xxVrfxn0g=
github.com/openziti/identity v1.0.60 h1:6gvBXY9J6F7SbuksdxsUA1t1WmtsFfY61Oqm/00ijGU=
github.com/openziti/identity v1.0.60/go.mod h1:pUfQ1Rf6TJvpBULXKPAO4014Qd6g+uf6V/vqjUscipU=
github.com/parallaxsecond/parsec-client-go v0.0.0-20220111122524-cb78842db373 h1:CUvH4JL/8OVy023LMER3dB/MerNQ6OIz4QV3E/JQ3UY=
github.com/parallaxsecond/parsec-client-go v0.0.0-20220111122524-cb78842db373/go.mod h1:gLH27qo/dvMhLTVVyMELpe3Tut7sOfkiDg7ZpeqKwsw=
github.com/parallaxsecond/parsec-client-go v0.0.0-20221025095442-f0a77d263cf9 h1:mOvehYivJ4Aqu2CPe3D3lv8jhqOI9/1o0THxJHBE0qw=
github.com/parallaxsecond/parsec-client-go v0.0.0-20221025095442-f0a77d263cf9/go.mod h1:gLH27qo/dvMhLTVVyMELpe3Tut7sOfkiDg7ZpeqKwsw=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
Expand Down Expand Up @@ -698,6 +699,7 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
Expand Down Expand Up @@ -901,8 +903,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
102 changes: 102 additions & 0 deletions proxies/http_connect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
Copyright NetFoundry 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.
*/

package proxies

import (
"bufio"
"github.com/michaelquigley/pfxlog"
"github.com/pkg/errors"
"golang.org/x/net/proxy"
"io"
"net"
"net/http"
"net/url"
)

func NewHttpConnectProxyDialer(addr string, auth *proxy.Auth) *HttpConnectProxyDialer {
return &HttpConnectProxyDialer{
address: addr,
auth: auth,
}
}

type HttpConnectProxyDialer struct {
address string
auth *proxy.Auth
}

func (self *HttpConnectProxyDialer) Dial(network, addr string) (net.Conn, error) {
c, err := net.Dial(network, self.address)
if err != nil {
return nil, errors.Wrapf(err, "unable to connect to proxy server at %s", self.address)
}

if err = self.Connect(c, addr); err != nil {
if closeErr := c.Close(); closeErr != nil {
pfxlog.Logger().WithError(closeErr).Error("failed to close connection to proxy after connect error")
}
return nil, err
}

return c, nil
}

func (self *HttpConnectProxyDialer) Connect(c net.Conn, addr string) error {
log := pfxlog.Logger()

log.Infof("create connect request to %s", addr)

//ctx, cancelF := context.WithTimeout(context.Background(), 10*time.Second)
//defer cancelF()

req := &http.Request{
Method: http.MethodConnect,
URL: &url.URL{Host: addr},
Host: addr,
Header: http.Header{},
Close: false,
}
// req = req.WithContext(ctx)
if self.auth != nil {
req.SetBasicAuth(self.auth.User, self.auth.Password)
}
req.Header.Set("User-Agent", "ziti-transport")

log.Info("writing request to wire")
if err := req.Write(c); err != nil {
return errors.Wrapf(err, "unable to send connect request to proxy server at %s", self.address)
}

log.Info("reading response from wire")
resp, err := http.ReadResponse(bufio.NewReader(c), req)
if err != nil {
return errors.Wrapf(err, "unable to read response to connect request to proxy server at %s", self.address)
}

defer func() {
log.Info("closing resp body")
_ = resp.Body.Close()
}()

if resp.StatusCode != http.StatusOK {
respBody, _ := io.ReadAll(resp.Body)
log.Errorf("proxy returned: %s", string(respBody))
return errors.Errorf("received %v instead of 200 OK in response to connect request to proxy server at %s", resp.StatusCode, self.address)
}

return nil
}
8 changes: 6 additions & 2 deletions tls/address.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
package tls

import (
"errors"
"fmt"
"github.com/openziti/identity"
"github.com/openziti/transport/v2"
"github.com/pkg/errors"
"io"
"strconv"
"strings"
Expand All @@ -41,7 +41,11 @@ func (a address) Dial(name string, i *identity.TokenId, timeout time.Duration, c
}

func (a address) DialWithLocalBinding(name string, localBinding string, i *identity.TokenId, timeout time.Duration, tcfg transport.Configuration) (transport.Conn, error) {
return DialWithLocalBinding(a.bindableAddress(), name, localBinding, i, timeout, tcfg.Protocols()...)
proxyConfig, err := tcfg.GetProxyConfiguration()
if err != nil {
return nil, errors.Wrapf(err, "unable to get proxy configuration")
}
return DialWithLocalBinding(a, name, localBinding, i, timeout, proxyConfig, tcfg.Protocols()...)
}

func (a address) Listen(name string, i *identity.TokenId, acceptF func(transport.Conn), tcfg transport.Configuration) (io.Closer, error) {
Expand Down
Loading

0 comments on commit 2c83bc8

Please sign in to comment.