Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for http connect proxying to tls connections. Fixes #54 #55

Merged
merged 1 commit into from
Aug 23, 2023
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
109 changes: 108 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,106 @@ func (self Configuration) Protocols() []string {
return nil
}

func (self Configuration) GetProxyConfiguration() (*ProxyConfiguration, error) {
if self == nil {
return nil, nil
}

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

val, found := self[KeyProxy]
if !found {
return nil, nil
}

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

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"
)

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
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
110 changes: 110 additions & 0 deletions proxies/http_connect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
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"
"context"
"github.com/michaelquigley/pfxlog"
"github.com/pkg/errors"
"golang.org/x/net/proxy"
"io"
"net"
"net/http"
"net/url"
"time"
)

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

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

func (self *HttpConnectProxyDialer) Dial(network, addr string) (net.Conn, error) {
c, err := net.Dial(network, self.address)
Copy link
Member

@andrewpmartinez andrewpmartinez Aug 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This assumes the proxy server is not TLS, correct? I believe that if the HTTP proxy is using TLS this will fail to connect properly.

It looks like the net.Conn from here is expected to be used in .Connect(), which sends credentials. If HTTP is only supported, those creds are sent in the clear.

Copy link
Member

@andrewpmartinez andrewpmartinez Aug 22, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One way to handle TLS vs non-TLS HTTP proxies is to require the protocol prefix on the address and inspect it:

https://proxyhost vs http://proxyhost and then do the right thing. However I don't know how well that pattern meshes with the transport codebase.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, this is just to get the bare minimum working. If/when we get more feature requests we can look at how to expand this out

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 := context.Background()
if self.timeout > 0 {
timeoutCtx, cancelF := context.WithTimeout(ctx, self.timeout)
defer cancelF()
ctx = timeoutCtx
}

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
Loading