Skip to content

Commit

Permalink
Modified implementation based on suggestions for review: #30274
Browse files Browse the repository at this point in the history
  • Loading branch information
htamakos committed Apr 27, 2022
1 parent e495be4 commit 4345d38
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 60 deletions.
2 changes: 1 addition & 1 deletion internal/communicator/shared/shared.go
Expand Up @@ -81,7 +81,7 @@ var ConnectionBlockSupersetSchema = &configschema.Block{
Optional: true,
},
"proxy_port": {
Type: cty.String,
Type: cty.Number,
Optional: true,
},
"proxy_user_name": {
Expand Down
4 changes: 2 additions & 2 deletions internal/communicator/ssh/communicator.go
Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
Expand Down
66 changes: 25 additions & 41 deletions internal/communicator/ssh/http_proxy.go
Expand Up @@ -2,7 +2,6 @@ package ssh

import (
"bufio"
"encoding/base64"
"fmt"
"net"
"net/http"
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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"
Expand All @@ -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)

Expand All @@ -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)
Expand All @@ -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.
Expand Down Expand Up @@ -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,
}
Expand All @@ -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
Expand Down
7 changes: 2 additions & 5 deletions internal/communicator/ssh/provisioner.go
Expand Up @@ -10,7 +10,6 @@ import (
"net"
"os"
"path/filepath"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -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":
Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion internal/terraform/node_resource_validate.go
Expand Up @@ -200,7 +200,7 @@ var connectionBlockSupersetSchema = &configschema.Block{
Optional: true,
},
"proxy_port": {
Type: cty.String,
Type: cty.Number,
Optional: true,
},
"proxy_user_name": {
Expand Down
17 changes: 7 additions & 10 deletions website/docs/language/resources/provisioners/connection.mdx
Expand Up @@ -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

Expand Down

0 comments on commit 4345d38

Please sign in to comment.