mirror of https://github.com/hashicorp/terraform
communicator/ssh: Add support SSH over HTTP Proxy (#30274)
Terraform's remote-exec provision hangs out when it execs on HTTP Proxy bacause it dosen't support SSH over HTTP Proxy. This commits enables Terraform's remote-exec to support SSH over HTTP Proxy. * adds `proxy_*` fields to `connection` which add configuration for a proxy host * if `proxy_host` set, connect to that proxy host via CONNECT method, then make the SSH connection to `host` or `bastion_host`pull/30954/merge
parent
de65cc43f3
commit
4cfb6bc893
@ -0,0 +1,152 @@
|
||||
package ssh
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/proxy"
|
||||
)
|
||||
|
||||
// Dialer implements for SSH over HTTP Proxy.
|
||||
type proxyDialer struct {
|
||||
proxy proxyInfo
|
||||
// forwarding Dialer
|
||||
forward proxy.Dialer
|
||||
}
|
||||
|
||||
type proxyInfo struct {
|
||||
// HTTP Proxy host or host:port
|
||||
host string
|
||||
// HTTP Proxy scheme
|
||||
scheme string
|
||||
// An immutable encapsulation of username and password details for a URL
|
||||
userInfo *url.Userinfo
|
||||
}
|
||||
|
||||
func newProxyInfo(host, scheme, username, password string) *proxyInfo {
|
||||
p := &proxyInfo{
|
||||
host: host,
|
||||
scheme: scheme,
|
||||
}
|
||||
|
||||
p.userInfo = url.UserPassword(username, password)
|
||||
|
||||
if p.scheme == "" {
|
||||
p.scheme = "http"
|
||||
}
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *proxyInfo) url() *url.URL {
|
||||
return &url.URL{
|
||||
Scheme: p.scheme,
|
||||
User: p.userInfo,
|
||||
Host: p.host,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *proxyDialer) Dial(network, addr string) (net.Conn, error) {
|
||||
// Dial the proxy host
|
||||
c, err := p.forward.Dial(network, p.proxy.host)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = c.SetDeadline(time.Now().Add(15 * time.Second))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Generate request URL to host accessed through the proxy
|
||||
reqUrl := &url.URL{
|
||||
Scheme: "",
|
||||
Host: addr,
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// If http proxy requires authentication, configure settings for basic authentication.
|
||||
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.
|
||||
req.Close = false
|
||||
|
||||
// Writes the request in the form expected by an HTTP proxy.
|
||||
err = req.Write(c)
|
||||
if err != nil {
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := http.ReadResponse(bufio.NewReader(c), req)
|
||||
|
||||
if err != nil {
|
||||
res.Body.Close()
|
||||
c.Close()
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res.Body.Close()
|
||||
|
||||
if res.StatusCode != http.StatusOK {
|
||||
c.Close()
|
||||
return nil, fmt.Errorf("Connection Error: StatusCode: %d", res.StatusCode)
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// NewHttpProxyDialer generate Http Proxy Dialer
|
||||
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 := &proxyDialer{
|
||||
proxy: *newProxyInfo(u.Host, u.Scheme, proxyUserName, proxyPassword),
|
||||
forward: forward,
|
||||
}
|
||||
|
||||
return pd, nil
|
||||
}
|
||||
|
||||
// RegisterDialerType register schemes used by `proxy.FromURL`
|
||||
func RegisterDialerType() {
|
||||
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) {
|
||||
pd, err := proxy.FromURL(p.url(), proxy.Direct)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proxyConn, err := pd.Dial("tcp", targetAddr)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return proxyConn, err
|
||||
}
|
||||
Loading…
Reference in new issue