diff --git a/internal/communicator/shared/shared.go b/internal/communicator/shared/shared.go index a57712a86e3b..5990807a7809 100644 --- a/internal/communicator/shared/shared.go +++ b/internal/communicator/shared/shared.go @@ -81,7 +81,7 @@ var ConnectionBlockSupersetSchema = &configschema.Block{ Optional: true, }, "proxy_port": { - Type: cty.String, + Type: cty.Number, Optional: true, }, "proxy_user_name": { diff --git a/internal/communicator/ssh/communicator.go b/internal/communicator/ssh/communicator.go index 0923a122d914..6200f4fa59b9 100644 --- a/internal/communicator/ssh/communicator.go +++ b/internal/communicator/ssh/communicator.go @@ -792,7 +792,7 @@ func ConnectFunc(network, addr string, p *proxyInfo) func() (net.Conn, error) { // Wrap connection to host if proxy server is configured if p != nil { RegisterDialerType() - c, err = NewHttpProxyConn(p, addr) + c, err = newHttpProxyConn(p, addr) } else { c, err = net.DialTimeout(network, addr, 15*time.Second) } @@ -831,7 +831,7 @@ func BastionConnectFunc( var bReq <-chan *ssh.Request RegisterDialerType() - pConn, err = NewHttpProxyConn(p, bAddr) + pConn, err = newHttpProxyConn(p, bAddr) if err != nil { return nil, fmt.Errorf("Error connecting to proxy: %s", err) diff --git a/internal/communicator/ssh/http_proxy.go b/internal/communicator/ssh/http_proxy.go index cc082121b83b..883dada50a9b 100644 --- a/internal/communicator/ssh/http_proxy.go +++ b/internal/communicator/ssh/http_proxy.go @@ -2,7 +2,6 @@ package ssh import ( "bufio" - "encoding/base64" "fmt" "net" "net/http" @@ -13,7 +12,7 @@ import ( ) // Dialer implements for SSH over HTTP Proxy. -type Dialer struct { +type proxyDialer struct { proxy proxyInfo // forwarding Dialer forward proxy.Dialer @@ -24,12 +23,8 @@ type proxyInfo struct { host string // HTTP Proxy scheme scheme string - // User name if http proxy needs authentication - username string - // User password if http proxy needs authentication - password string - // Whether the HTTP Proxy requires authentication - auth bool + // An immutable encapsulation of username and password details for a URL + userInfo *url.Userinfo } func newProxyInfo(host, scheme, username, password string) *proxyInfo { @@ -38,11 +33,7 @@ func newProxyInfo(host, scheme, username, password string) *proxyInfo { scheme: scheme, } - if username != "" && password != "" { - p.auth = true - p.username = username - p.password = password - } + p.userInfo = url.UserPassword(username, password) if p.scheme == "" { p.scheme = "http" @@ -51,17 +42,15 @@ func newProxyInfo(host, scheme, username, password string) *proxyInfo { return p } -func (p *proxyInfo) url() (*url.URL, error) { - base := p.scheme + "://" - - if p.auth { - base = base + p.username + ":" + p.password + "@" +func (p *proxyInfo) url() *url.URL { + return &url.URL{ + Scheme: p.scheme, + User: p.userInfo, + Host: p.host, } - - return url.Parse(base + p.host) } -func (p *Dialer) Dial(network, addr string) (net.Conn, error) { +func (p *proxyDialer) Dial(network, addr string) (net.Conn, error) { // Dial the proxy host c, err := p.forward.Dial(network, p.proxy.host) @@ -75,12 +64,10 @@ func (p *Dialer) Dial(network, addr string) (net.Conn, error) { } // Generate request URL to host accessed through the proxy - reqUrl, err := url.Parse("http://" + addr) - if err != nil { - c.Close() - return nil, err + reqUrl := &url.URL{ + Scheme: "", + Host: addr, } - reqUrl.Scheme = "" // Create a request object using the CONNECT method to instruct the proxy server to tunnel a protocol other than HTTP. req, err := http.NewRequest("CONNECT", reqUrl.String(), nil) @@ -90,9 +77,11 @@ func (p *Dialer) Dial(network, addr string) (net.Conn, error) { } // If http proxy requires authentication, configure settings for basic authentication. - if p.proxy.auth { - req.SetBasicAuth(p.proxy.username, p.proxy.password) - req.Header.Add("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(p.proxy.username+":"+p.proxy.password))) + if p.proxy.userInfo.String() != "" { + username := p.proxy.userInfo.Username() + password, _ := p.proxy.userInfo.Password() + req.SetBasicAuth(username, password) + req.Header.Add("Proxy-Authorization", req.Header.Get("Authorization")) } // Do not close the connection after sending this request and reading its response. @@ -124,14 +113,14 @@ func (p *Dialer) Dial(network, addr string) (net.Conn, error) { } // NewHttpProxyDialer generate Http Proxy Dialer -func NewHttpProxyDialer(u *url.URL, forward proxy.Dialer) (proxy.Dialer, error) { +func newHttpProxyDialer(u *url.URL, forward proxy.Dialer) (proxy.Dialer, error) { var proxyUserName, proxyPassword string if u.User != nil { proxyUserName = u.User.Username() proxyPassword, _ = u.User.Password() } - pd := &Dialer{ + pd := &proxyDialer{ proxy: *newProxyInfo(u.Host, u.Scheme, proxyUserName, proxyPassword), forward: forward, } @@ -141,24 +130,19 @@ func NewHttpProxyDialer(u *url.URL, forward proxy.Dialer) (proxy.Dialer, error) // RegisterDialerType register schemes used by `proxy.FromURL` func RegisterDialerType() { - proxy.RegisterDialerType("http", NewHttpProxyDialer) - proxy.RegisterDialerType("https", NewHttpProxyDialer) + proxy.RegisterDialerType("http", newHttpProxyDialer) + proxy.RegisterDialerType("https", newHttpProxyDialer) } // NewHttpProxyConn create a connection to connect through the proxy server. -func NewHttpProxyConn(p *proxyInfo, targetAddr string) (net.Conn, error) { - proxyURL, err := p.url() - if err != nil { - return nil, err - } - - proxyDialer, err := proxy.FromURL(proxyURL, proxy.Direct) +func newHttpProxyConn(p *proxyInfo, targetAddr string) (net.Conn, error) { + pd, err := proxy.FromURL(p.url(), proxy.Direct) if err != nil { return nil, err } - proxyConn, err := proxyDialer.Dial("tcp", targetAddr) + proxyConn, err := pd.Dial("tcp", targetAddr) if err != nil { return nil, err diff --git a/internal/communicator/ssh/provisioner.go b/internal/communicator/ssh/provisioner.go index 45f94a5d5a08..b98ee9f5d2cb 100644 --- a/internal/communicator/ssh/provisioner.go +++ b/internal/communicator/ssh/provisioner.go @@ -10,7 +10,6 @@ import ( "net" "os" "path/filepath" - "strconv" "strings" "time" @@ -124,11 +123,9 @@ func decodeConnInfo(v cty.Value) (*connectionInfo, error) { case "proxy_host": connInfo.ProxyHost = v.AsString() case "proxy_port": - p, err := strconv.ParseUint(v.AsString(), 10, 16) - if err != nil { + if err := gocty.FromCtyValue(v, &connInfo.ProxyPort); err != nil { return nil, err } - connInfo.ProxyPort = uint16(p) case "proxy_user_name": connInfo.ProxyUserName = v.AsString() case "proxy_user_password": @@ -279,7 +276,7 @@ func prepareSSHConfig(connInfo *connectionInfo) (*sshConfig, error) { if connInfo.ProxyHost != "" { p = newProxyInfo( - connInfo.ProxyHost+":"+strconv.FormatUint(uint64(connInfo.ProxyPort), 10), + fmt.Sprintf("%s:%d", connInfo.ProxyHost, connInfo.ProxyPort), connInfo.ProxyScheme, connInfo.ProxyUserName, connInfo.ProxyUserPassword, diff --git a/internal/terraform/node_resource_validate.go b/internal/terraform/node_resource_validate.go index fa43b32ed13c..a70bcdc4bc85 100644 --- a/internal/terraform/node_resource_validate.go +++ b/internal/terraform/node_resource_validate.go @@ -200,7 +200,7 @@ var connectionBlockSupersetSchema = &configschema.Block{ Optional: true, }, "proxy_port": { - Type: cty.String, + Type: cty.Number, Optional: true, }, "proxy_user_name": { diff --git a/website/docs/language/resources/provisioners/connection.mdx b/website/docs/language/resources/provisioners/connection.mdx index 6de52deef184..655f617e3d71 100644 --- a/website/docs/language/resources/provisioners/connection.mdx +++ b/website/docs/language/resources/provisioners/connection.mdx @@ -117,16 +117,13 @@ indirectly with a [bastion host](https://en.wikipedia.org/wiki/Bastion_host). The `ssh` connection also supports the following fields to facilitate connections by SSH over HTTP proxy. -* `proxy_scheme` - http or https - -* `proxy_host` - Setting this enables the SSH over HTTP connection. This host - will be connected to first, and then the `host` or `bastion_host` connection will be made from there. - -* `proxy_port` - The port to use connect to the proxy host. - -* `proxy_user_name` - The username to use connect to the private proxy host. - -* `proxy_user_password` - The password to use connect to the private proxy host. +| Argument | Description | Default | +|---------------|-------------|---------| +| `proxy_scheme` | http or https | | +| `proxy_host` | Setting this enables the SSH over HTTP connection. This host will be connected to first, and then the `host` or `bastion_host` connection will be made from there. | | +| `proxy_port` | The port to use connect to the proxy host. | | +| `proxy_user_name` | The username to use connect to the private proxy host. This argument should be specified only if authentication is required for the HTTP Proxy server. | | +| `proxy_user_password` | The password to use connect to the private proxy host. This argument should be specified only if authentication is required for the HTTP Proxy server. | | ## How Provisioners Execute Remote Scripts