mirror of
https://github.com/gravitational/teleport
synced 2024-10-21 01:34:01 +00:00
Join address for web, reverse tunnel, fixes #1544
Support configuration for web and reverse tunnel proxies to listen on the same port. * Default config are not changed for backwards compatibility. * If administrator configures web and reverse tunnel addresses to be on the same port, multiplexing is turned on * In trusted clusters configuration reverse_tunnel_addr defaults to web_addr.
This commit is contained in:
parent
19a6e5ed4b
commit
ef473d809e
|
@ -108,45 +108,63 @@ func (s *InstanceSecrets) String() string {
|
|||
return string(bytes)
|
||||
}
|
||||
|
||||
// InstanceConfig is an instance configuration
|
||||
type InstanceConfig struct {
|
||||
// ClusterName is a cluster name of the instance
|
||||
ClusterName string
|
||||
// HostID is a host id of the instance
|
||||
HostID string
|
||||
// NodeName is a node name of the instance
|
||||
NodeName string
|
||||
// Ports is a list of assigned ports to use
|
||||
Ports []int
|
||||
// Priv is SSH private key of the instance
|
||||
Priv []byte
|
||||
// Pub is SSH public key of the instance
|
||||
Pub []byte
|
||||
// MultiplexProxy uses the same port for web and SSH reverse tunnel proxy
|
||||
MultiplexProxy bool
|
||||
}
|
||||
|
||||
// NewInstance creates a new Teleport process instance
|
||||
func NewInstance(clusterName string, hostID string, nodeName string, ports []int, priv, pub []byte) *TeleInstance {
|
||||
func NewInstance(cfg InstanceConfig) *TeleInstance {
|
||||
var err error
|
||||
if len(ports) < 5 {
|
||||
fatalIf(fmt.Errorf("not enough free ports given: %v", ports))
|
||||
if len(cfg.Ports) < 5 {
|
||||
fatalIf(fmt.Errorf("not enough free ports given: %v", cfg.Ports))
|
||||
}
|
||||
if nodeName == "" {
|
||||
nodeName, err = os.Hostname()
|
||||
if cfg.NodeName == "" {
|
||||
cfg.NodeName, err = os.Hostname()
|
||||
fatalIf(err)
|
||||
}
|
||||
// generate instance secrets (keys):
|
||||
keygen := native.New()
|
||||
if priv == nil || pub == nil {
|
||||
priv, pub, _ = keygen.GenerateKeyPair("")
|
||||
if cfg.Priv == nil || cfg.Pub == nil {
|
||||
cfg.Priv, cfg.Pub, _ = keygen.GenerateKeyPair("")
|
||||
}
|
||||
rsaKey, err := ssh.ParseRawPrivateKey(priv)
|
||||
rsaKey, err := ssh.ParseRawPrivateKey(cfg.Priv)
|
||||
fatalIf(err)
|
||||
|
||||
tlsCAKey, tlsCACert, err := tlsca.GenerateSelfSignedCAWithPrivateKey(rsaKey.(*rsa.PrivateKey), pkix.Name{
|
||||
CommonName: clusterName,
|
||||
Organization: []string{clusterName},
|
||||
CommonName: cfg.ClusterName,
|
||||
Organization: []string{cfg.ClusterName},
|
||||
}, nil, defaults.CATTL)
|
||||
fatalIf(err)
|
||||
|
||||
cert, err := keygen.GenerateHostCert(services.HostCertParams{
|
||||
PrivateCASigningKey: priv,
|
||||
PublicHostKey: pub,
|
||||
HostID: hostID,
|
||||
NodeName: nodeName,
|
||||
ClusterName: clusterName,
|
||||
PrivateCASigningKey: cfg.Priv,
|
||||
PublicHostKey: cfg.Pub,
|
||||
HostID: cfg.HostID,
|
||||
NodeName: cfg.NodeName,
|
||||
ClusterName: cfg.ClusterName,
|
||||
Roles: teleport.Roles{teleport.RoleAdmin},
|
||||
TTL: time.Duration(time.Hour * 24),
|
||||
})
|
||||
fatalIf(err)
|
||||
tlsCA, err := tlsca.New(tlsCACert, tlsCAKey)
|
||||
fatalIf(err)
|
||||
cryptoPubKey, err := sshutils.CryptoPublicKey(pub)
|
||||
cryptoPubKey, err := sshutils.CryptoPublicKey(cfg.Pub)
|
||||
identity := tlsca.Identity{
|
||||
Username: fmt.Sprintf("%v.%v", hostID, clusterName),
|
||||
Username: fmt.Sprintf("%v.%v", cfg.HostID, cfg.ClusterName),
|
||||
Groups: []string{string(teleport.RoleAdmin)},
|
||||
}
|
||||
clock := clockwork.NewRealClock()
|
||||
|
@ -159,20 +177,23 @@ func NewInstance(clusterName string, hostID string, nodeName string, ports []int
|
|||
fatalIf(err)
|
||||
|
||||
i := &TeleInstance{
|
||||
Ports: ports,
|
||||
Hostname: nodeName,
|
||||
Ports: cfg.Ports,
|
||||
Hostname: cfg.NodeName,
|
||||
}
|
||||
secrets := InstanceSecrets{
|
||||
SiteName: clusterName,
|
||||
PrivKey: priv,
|
||||
PubKey: pub,
|
||||
SiteName: cfg.ClusterName,
|
||||
PrivKey: cfg.Priv,
|
||||
PubKey: cfg.Pub,
|
||||
Cert: cert,
|
||||
TLSCACert: tlsCACert,
|
||||
TLSCert: tlsCert,
|
||||
ListenAddr: net.JoinHostPort(nodeName, strconv.Itoa(ports[4])),
|
||||
WebProxyAddr: net.JoinHostPort(nodeName, i.GetPortWeb()),
|
||||
ListenAddr: net.JoinHostPort(cfg.NodeName, i.GetPortReverseTunnel()),
|
||||
WebProxyAddr: net.JoinHostPort(cfg.NodeName, i.GetPortWeb()),
|
||||
Users: make(map[string]*User),
|
||||
}
|
||||
if cfg.MultiplexProxy {
|
||||
secrets.ListenAddr = secrets.WebProxyAddr
|
||||
}
|
||||
i.Secrets = secrets
|
||||
return i
|
||||
}
|
||||
|
@ -258,6 +279,10 @@ func (i *TeleInstance) GetPortWeb() string {
|
|||
return strconv.Itoa(i.Ports[3])
|
||||
}
|
||||
|
||||
func (i *TeleInstance) GetPortReverseTunnel() string {
|
||||
return strconv.Itoa(i.Ports[4])
|
||||
}
|
||||
|
||||
// GetSiteAPI() is a helper which returns an API endpoint to a site with
|
||||
// a given name. i endpoint implements HTTP-over-SSH access to the
|
||||
// site's auth server.
|
||||
|
|
|
@ -114,7 +114,7 @@ func (s *IntSuite) SetUpSuite(c *check.C) {
|
|||
// newTeleport helper returns a running Teleport instance pre-configured
|
||||
// with the current user os.user.Current().
|
||||
func (s *IntSuite) newTeleport(c *check.C, logins []string, enableSSH bool) *TeleInstance {
|
||||
t := NewInstance(Site, HostID, Host, s.getPorts(5), s.priv, s.pub)
|
||||
t := NewInstance(InstanceConfig{ClusterName: Site, HostID: HostID, NodeName: Host, Ports: s.getPorts(5), Priv: s.priv, Pub: s.pub})
|
||||
// use passed logins, but use suite's default login if nothing was passed
|
||||
if logins == nil || len(logins) == 0 {
|
||||
logins = []string{s.me.Username}
|
||||
|
@ -135,7 +135,7 @@ func (s *IntSuite) newTeleport(c *check.C, logins []string, enableSSH bool) *Tel
|
|||
// Teleport instance with the passed in user, instance secrets, and Teleport
|
||||
// configuration.
|
||||
func (s *IntSuite) newTeleportWithConfig(c *check.C, logins []string, instanceSecrets []*InstanceSecrets, teleportConfig *service.Config) *TeleInstance {
|
||||
t := NewInstance(Site, HostID, Host, s.getPorts(5), s.priv, s.pub)
|
||||
t := NewInstance(InstanceConfig{ClusterName: Site, HostID: HostID, NodeName: Host, Ports: s.getPorts(5), Priv: s.priv, Pub: s.pub})
|
||||
|
||||
// use passed logins, but use suite's default login if nothing was passed
|
||||
if logins == nil || len(logins) == 0 {
|
||||
|
@ -617,8 +617,8 @@ func (s *IntSuite) TestTwoClusters(c *check.C) {
|
|||
|
||||
username := s.me.Username
|
||||
|
||||
a := NewInstance("site-A", HostID, Host, s.getPorts(5), s.priv, s.pub)
|
||||
b := NewInstance("site-B", HostID, Host, s.getPorts(5), s.priv, s.pub)
|
||||
a := NewInstance(InstanceConfig{ClusterName: "site-A", HostID: HostID, NodeName: Host, Ports: s.getPorts(5), Priv: s.priv, Pub: s.pub})
|
||||
b := NewInstance(InstanceConfig{ClusterName: "site-B", HostID: HostID, NodeName: Host, Ports: s.getPorts(5), Priv: s.priv, Pub: s.pub})
|
||||
|
||||
a.AddUser(username, []string{username})
|
||||
b.AddUser(username, []string{username})
|
||||
|
@ -776,8 +776,8 @@ func (s *IntSuite) TestTwoClustersProxy(c *check.C) {
|
|||
|
||||
username := s.me.Username
|
||||
|
||||
a := NewInstance("site-A", HostID, Host, s.getPorts(5), s.priv, s.pub)
|
||||
b := NewInstance("site-B", HostID, Host, s.getPorts(5), s.priv, s.pub)
|
||||
a := NewInstance(InstanceConfig{ClusterName: "site-A", HostID: HostID, NodeName: Host, Ports: s.getPorts(5), Priv: s.priv, Pub: s.pub})
|
||||
b := NewInstance(InstanceConfig{ClusterName: "site-B", HostID: HostID, NodeName: Host, Ports: s.getPorts(5), Priv: s.priv, Pub: s.pub})
|
||||
|
||||
a.AddUser(username, []string{username})
|
||||
b.AddUser(username, []string{username})
|
||||
|
@ -810,8 +810,8 @@ func (s *IntSuite) TestTwoClustersProxy(c *check.C) {
|
|||
func (s *IntSuite) TestHA(c *check.C) {
|
||||
username := s.me.Username
|
||||
|
||||
a := NewInstance("cluster-a", HostID, Host, s.getPorts(5), s.priv, s.pub)
|
||||
b := NewInstance("cluster-b", HostID, Host, s.getPorts(5), s.priv, s.pub)
|
||||
a := NewInstance(InstanceConfig{ClusterName: "cluster-a", HostID: HostID, NodeName: Host, Ports: s.getPorts(5), Priv: s.priv, Pub: s.pub})
|
||||
b := NewInstance(InstanceConfig{ClusterName: "cluster-b", HostID: HostID, NodeName: Host, Ports: s.getPorts(5), Priv: s.priv, Pub: s.pub})
|
||||
|
||||
a.AddUser(username, []string{username})
|
||||
b.AddUser(username, []string{username})
|
||||
|
@ -879,8 +879,8 @@ func (s *IntSuite) TestMapRoles(c *check.C) {
|
|||
|
||||
clusterMain := "cluster-main"
|
||||
clusterAux := "cluster-aux"
|
||||
main := NewInstance(clusterMain, HostID, Host, s.getPorts(5), s.priv, s.pub)
|
||||
aux := NewInstance(clusterAux, HostID, Host, s.getPorts(5), s.priv, s.pub)
|
||||
main := NewInstance(InstanceConfig{ClusterName: clusterMain, HostID: HostID, NodeName: Host, Ports: s.getPorts(5), Priv: s.priv, Pub: s.pub})
|
||||
aux := NewInstance(InstanceConfig{ClusterName: clusterAux, HostID: HostID, NodeName: Host, Ports: s.getPorts(5), Priv: s.priv, Pub: s.pub})
|
||||
|
||||
// main cluster has a local user and belongs to role "main-devs"
|
||||
mainDevs := "main-devs"
|
||||
|
@ -1067,15 +1067,25 @@ func (s *IntSuite) TestMapRoles(c *check.C) {
|
|||
c.Assert(aux.Stop(true), check.IsNil)
|
||||
}
|
||||
|
||||
// TestRemoteClusters tests disconnecting remote clusters
|
||||
// using remote cluster feature
|
||||
func (s *IntSuite) TestRemoteClusters(c *check.C) {
|
||||
// TestTrustedClusters tests remote clusters scenarios
|
||||
// using trusted clusters feature
|
||||
func (s *IntSuite) TestTrustedClusters(c *check.C) {
|
||||
s.trustedClusters(c, false)
|
||||
}
|
||||
|
||||
// TestMultiplexingTrustedClusters tests remote clusters scenarios
|
||||
// using trusted clusters feature
|
||||
func (s *IntSuite) TestMultiplexingTrustedClusters(c *check.C) {
|
||||
s.trustedClusters(c, true)
|
||||
}
|
||||
|
||||
func (s *IntSuite) trustedClusters(c *check.C, multiplex bool) {
|
||||
username := s.me.Username
|
||||
|
||||
clusterMain := "cluster-main"
|
||||
clusterAux := "cluster-aux"
|
||||
main := NewInstance(clusterMain, HostID, Host, s.getPorts(5), s.priv, s.pub)
|
||||
aux := NewInstance(clusterAux, HostID, Host, s.getPorts(5), s.priv, s.pub)
|
||||
main := NewInstance(InstanceConfig{ClusterName: clusterMain, HostID: HostID, NodeName: Host, Ports: s.getPorts(5), Priv: s.priv, Pub: s.pub, MultiplexProxy: multiplex})
|
||||
aux := NewInstance(InstanceConfig{ClusterName: clusterAux, HostID: HostID, NodeName: Host, Ports: s.getPorts(5), Priv: s.priv, Pub: s.pub})
|
||||
|
||||
// main cluster has a local user and belongs to role "main-devs"
|
||||
mainDevs := "main-devs"
|
||||
|
@ -1254,8 +1264,8 @@ func (s *IntSuite) TestDiscovery(c *check.C) {
|
|||
go lb.Serve()
|
||||
defer lb.Close()
|
||||
|
||||
remote := NewInstance("cluster-remote", HostID, Host, s.getPorts(5), s.priv, s.pub)
|
||||
main := NewInstance("cluster-main", HostID, Host, s.getPorts(5), s.priv, s.pub)
|
||||
remote := NewInstance(InstanceConfig{ClusterName: "cluster-remote", HostID: HostID, NodeName: Host, Ports: s.getPorts(5), Priv: s.priv, Pub: s.pub})
|
||||
main := NewInstance(InstanceConfig{ClusterName: "cluster-main", HostID: HostID, NodeName: Host, Ports: s.getPorts(5), Priv: s.priv, Pub: s.pub})
|
||||
|
||||
remote.AddUser(username, []string{username})
|
||||
main.AddUser(username, []string{username})
|
||||
|
|
|
@ -261,6 +261,10 @@ func ApplyFileConfig(fc *FileConfig, cfg *service.Config) error {
|
|||
}
|
||||
|
||||
// apply "proxy_service" section
|
||||
cfg.Proxy.EnableProxyProtocol, err = utils.ParseOnOff("proxy_protocol", fc.Proxy.ProxyProtocol, true)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
if fc.Proxy.ListenAddress != "" {
|
||||
addr, err := utils.ParseHostPortAddr(fc.Proxy.ListenAddress, int(defaults.SSHProxyListenPort))
|
||||
if err != nil {
|
||||
|
|
|
@ -654,14 +654,25 @@ type CommandLabel struct {
|
|||
Period time.Duration `yaml:"period"`
|
||||
}
|
||||
|
||||
// Proxy is `proxy_service` section of the config file:
|
||||
// Proxy is a `proxy_service` section of the config file:
|
||||
type Proxy struct {
|
||||
Service `yaml:",inline"`
|
||||
WebAddr string `yaml:"web_listen_addr,omitempty"`
|
||||
TunAddr string `yaml:"tunnel_listen_addr,omitempty"`
|
||||
KeyFile string `yaml:"https_key_file,omitempty"`
|
||||
CertFile string `yaml:"https_cert_file,omitempty"`
|
||||
// Service is a generic service configuration section
|
||||
Service `yaml:",inline"`
|
||||
// WebAddr is a web UI listen address
|
||||
WebAddr string `yaml:"web_listen_addr,omitempty"`
|
||||
// TunAddr is a reverse tunnel address
|
||||
TunAddr string `yaml:"tunnel_listen_addr,omitempty"`
|
||||
// KeyFile is a TLS key file
|
||||
KeyFile string `yaml:"https_key_file,omitempty"`
|
||||
// CertFile is a TLS Certificate file
|
||||
CertFile string `yaml:"https_cert_file,omitempty"`
|
||||
// PublicAddr is a publicly advertised address of the proxy
|
||||
PublicAddr string `yaml:"public_addr,omitempty"`
|
||||
// ProxyProtocol turns on support for HAProxy proxy protocol
|
||||
// this is the option that has be turned on only by administrator,
|
||||
// as only admin knows whether service is in front of trusted load balancer
|
||||
// or not.
|
||||
ProxyProtocol string `yaml:"proxy_protocol,omitempty"`
|
||||
}
|
||||
|
||||
// ReverseTunnel is a SSH reverse tunnel maintained by one cluster's
|
||||
|
|
|
@ -53,6 +53,10 @@ type Config struct {
|
|||
Clock clockwork.Clock
|
||||
// EnableProxyProtocol enables proxy protocol
|
||||
EnableProxyProtocol bool
|
||||
// DisableSSH disables SSH socket
|
||||
DisableSSH bool
|
||||
// DisableTLS disables TLS socket
|
||||
DisableTLS bool
|
||||
}
|
||||
|
||||
// CheckAndSetDefaults verifies configuration and sets defaults
|
||||
|
@ -161,13 +165,13 @@ func (m *Mux) Serve() error {
|
|||
for {
|
||||
conn, err := m.Listener.Accept()
|
||||
if err == nil {
|
||||
if tcpConn, ok := conn.(*net.TCPConn); ok {
|
||||
tcpConn.SetKeepAlive(true)
|
||||
tcpConn.SetKeepAlivePeriod(3 * time.Minute)
|
||||
}
|
||||
go m.detectAndForward(conn)
|
||||
continue
|
||||
}
|
||||
if tcpConn, ok := conn.(*net.TCPConn); ok {
|
||||
tcpConn.SetKeepAlive(true)
|
||||
tcpConn.SetKeepAlivePeriod(3 * time.Minute)
|
||||
}
|
||||
if m.isClosed() {
|
||||
return nil
|
||||
}
|
||||
|
@ -205,6 +209,11 @@ func (m *Mux) detectAndForward(conn net.Conn) {
|
|||
|
||||
switch connWrapper.protocol {
|
||||
case ProtoTLS:
|
||||
if m.DisableTLS {
|
||||
m.Debug("Closing TLS connection: TLS listener is disabled.")
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
select {
|
||||
case m.tlsListener.connC <- connWrapper:
|
||||
case <-m.context.Done():
|
||||
|
@ -212,6 +221,11 @@ func (m *Mux) detectAndForward(conn net.Conn) {
|
|||
return
|
||||
}
|
||||
case ProtoSSH:
|
||||
if m.DisableSSH {
|
||||
m.Debug("Closing SSH connection: SSH listener is disabled.")
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
select {
|
||||
case m.sshListener.connC <- connWrapper:
|
||||
case <-m.context.Done():
|
||||
|
|
|
@ -309,6 +309,121 @@ func (s *MuxSuite) TestUnknownProtocol(c *check.C) {
|
|||
c.Assert(err, check.Equals, io.EOF)
|
||||
}
|
||||
|
||||
// TestDisableSSH disables SSH
|
||||
func (s *MuxSuite) TestDisableSSH(c *check.C) {
|
||||
ports, err := utils.GetFreeTCPPorts(1)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
listener, err := net.Listen("tcp", net.JoinHostPort("127.0.0.1", ports[0]))
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
mux, err := New(Config{
|
||||
Listener: listener,
|
||||
EnableProxyProtocol: true,
|
||||
DisableSSH: true,
|
||||
})
|
||||
c.Assert(err, check.IsNil)
|
||||
go mux.Serve()
|
||||
defer mux.Close()
|
||||
|
||||
backend1 := &httptest.Server{
|
||||
Listener: mux.TLS(),
|
||||
Config: &http.Server{Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "backend 1")
|
||||
}),
|
||||
},
|
||||
}
|
||||
backend1.StartTLS()
|
||||
defer backend1.Close()
|
||||
|
||||
_, err = ssh.Dial("tcp", listener.Addr().String(), &ssh.ClientConfig{
|
||||
Auth: []ssh.AuthMethod{ssh.Password("abc123")},
|
||||
Timeout: time.Second,
|
||||
})
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
// TLS requests will succeed
|
||||
client := testClient(backend1)
|
||||
re, err := client.Get(backend1.URL)
|
||||
c.Assert(err, check.IsNil)
|
||||
bytes, err := ioutil.ReadAll(re.Body)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(string(bytes), check.Equals, "backend 1")
|
||||
|
||||
// Close mux, new requests should fail
|
||||
mux.Close()
|
||||
mux.Wait()
|
||||
|
||||
// use new client to use new connection pool
|
||||
client = testClient(backend1)
|
||||
_, err = client.Get(backend1.URL)
|
||||
c.Assert(err, check.NotNil)
|
||||
}
|
||||
|
||||
// TestDisableTLS tests scenario with disabled TLS
|
||||
func (s *MuxSuite) TestDisableTLS(c *check.C) {
|
||||
ports, err := utils.GetFreeTCPPorts(1)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
listener, err := net.Listen("tcp", net.JoinHostPort("127.0.0.1", ports[0]))
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
mux, err := New(Config{
|
||||
Listener: listener,
|
||||
EnableProxyProtocol: true,
|
||||
DisableTLS: true,
|
||||
})
|
||||
c.Assert(err, check.IsNil)
|
||||
go mux.Serve()
|
||||
defer mux.Close()
|
||||
|
||||
backend1 := &httptest.Server{
|
||||
Listener: mux.TLS(),
|
||||
Config: &http.Server{Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "backend 1")
|
||||
}),
|
||||
},
|
||||
}
|
||||
backend1.StartTLS()
|
||||
defer backend1.Close()
|
||||
|
||||
called := false
|
||||
sshHandler := sshutils.NewChanHandlerFunc(func(_ net.Conn, conn *ssh.ServerConn, nch ssh.NewChannel) {
|
||||
called = true
|
||||
nch.Reject(ssh.Prohibited, "nothing to see here")
|
||||
})
|
||||
|
||||
srv, err := sshutils.NewServer(
|
||||
"test",
|
||||
utils.NetAddr{AddrNetwork: "tcp", Addr: "localhost:0"},
|
||||
sshHandler,
|
||||
s.signers,
|
||||
sshutils.AuthMethods{Password: pass("abc123")},
|
||||
)
|
||||
c.Assert(err, check.IsNil)
|
||||
go srv.Serve(mux.SSH())
|
||||
defer srv.Close()
|
||||
clt, err := ssh.Dial("tcp", listener.Addr().String(), &ssh.ClientConfig{
|
||||
Auth: []ssh.AuthMethod{ssh.Password("abc123")},
|
||||
Timeout: time.Second,
|
||||
})
|
||||
c.Assert(err, check.IsNil)
|
||||
defer clt.Close()
|
||||
|
||||
// call new session to initiate opening new channel
|
||||
clt.NewSession()
|
||||
// make sure the channel handler was called OK
|
||||
c.Assert(called, check.Equals, true)
|
||||
|
||||
client := testClient(backend1)
|
||||
_, err = client.Get(backend1.URL)
|
||||
c.Assert(err, check.NotNil)
|
||||
|
||||
// Close mux, new requests should fail
|
||||
mux.Close()
|
||||
mux.Wait()
|
||||
}
|
||||
|
||||
// clientConfig returns tls client config from test http server
|
||||
// set up to listen on TLS
|
||||
func clientConfig(srv *httptest.Server) *tls.Config {
|
||||
|
|
|
@ -104,8 +104,8 @@ type Config struct {
|
|||
// ClientTLS is a TLS config associated with this proxy
|
||||
// used to connect to remote auth servers on remote clusters
|
||||
ClientTLS *tls.Config
|
||||
// ListenAddr is a listening address for reverse tunnel server
|
||||
ListenAddr utils.NetAddr
|
||||
// Listener is a listener address for reverse tunnel server
|
||||
Listener net.Listener
|
||||
// HostSigners is a list of host signers
|
||||
HostSigners []ssh.Signer
|
||||
// HostKeyCallback
|
||||
|
@ -152,8 +152,8 @@ func (cfg *Config) CheckAndSetDefaults() error {
|
|||
if cfg.ClientTLS == nil {
|
||||
return trace.BadParameter("missing parameter ClientTLS")
|
||||
}
|
||||
if cfg.ListenAddr.IsEmpty() {
|
||||
return trace.BadParameter("missing parameter ListenAddr")
|
||||
if cfg.Listener == nil {
|
||||
return trace.BadParameter("missing parameter Listener")
|
||||
}
|
||||
if cfg.Context == nil {
|
||||
cfg.Context = context.TODO()
|
||||
|
@ -205,7 +205,9 @@ func NewServer(cfg Config) (Server, error) {
|
|||
var err error
|
||||
s, err := sshutils.NewServer(
|
||||
teleport.ComponentReverseTunnelServer,
|
||||
cfg.ListenAddr,
|
||||
// TODO(klizhentas): improve interface, use struct instead of parameter list
|
||||
// this address is not used
|
||||
utils.NetAddr{Addr: "127.0.0.1:1", AddrNetwork: "tcp"},
|
||||
srv,
|
||||
cfg.HostSigners,
|
||||
sshutils.AuthMethods{
|
||||
|
@ -404,7 +406,8 @@ func (s *server) Wait() {
|
|||
}
|
||||
|
||||
func (s *server) Start() error {
|
||||
return s.srv.Start()
|
||||
go s.srv.Serve(s.Listener)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) Close() error {
|
||||
|
|
|
@ -210,6 +210,9 @@ type ProxyConfig struct {
|
|||
// ReverseTunnelListenAddr is address where reverse tunnel dialers connect to
|
||||
ReverseTunnelListenAddr utils.NetAddr
|
||||
|
||||
// EnableProxyProtocol enables proxy protocol support
|
||||
EnableProxyProtocol bool
|
||||
|
||||
// WebAddr is address for web portal of the proxy
|
||||
WebAddr utils.NetAddr
|
||||
|
||||
|
|
|
@ -808,6 +808,99 @@ func (process *TeleportProcess) initProxy() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
type proxyListeners struct {
|
||||
mux *multiplexer.Mux
|
||||
web net.Listener
|
||||
reverseTunnel net.Listener
|
||||
}
|
||||
|
||||
func (l *proxyListeners) Close() {
|
||||
if l.mux != nil {
|
||||
l.mux.Close()
|
||||
}
|
||||
if l.web != nil {
|
||||
l.web.Close()
|
||||
}
|
||||
if l.reverseTunnel != nil {
|
||||
l.reverseTunnel.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// setupProxyListeners sets up web proxy listeners based on the configuration
|
||||
func (process *TeleportProcess) setupProxyListeners() (*proxyListeners, error) {
|
||||
cfg := process.Config
|
||||
log.Debugf("Setup Proxy: Web Proxy Address: %v, Reverse Tunnel Proxy Address: %v", cfg.Proxy.WebAddr.Addr, cfg.Proxy.ReverseTunnelListenAddr.Addr)
|
||||
var err error
|
||||
var listeners proxyListeners
|
||||
switch {
|
||||
case cfg.Proxy.DisableWebService && cfg.Proxy.DisableReverseTunnel:
|
||||
log.Debugf("Setup Proxy: Reverse tunnel proxy and web proxy are disabled.")
|
||||
return &listeners, nil
|
||||
case cfg.Proxy.ReverseTunnelListenAddr.Equals(cfg.Proxy.WebAddr):
|
||||
log.Debugf("Setup Proxy: Reverse tunnel proxy and web proxy listen on the same port, multiplexing is on.")
|
||||
listener, err := net.Listen("tcp", cfg.Proxy.WebAddr.Addr)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
listeners.mux, err = multiplexer.New(multiplexer.Config{
|
||||
EnableProxyProtocol: cfg.Proxy.EnableProxyProtocol,
|
||||
Listener: listener,
|
||||
DisableTLS: cfg.Proxy.DisableWebService,
|
||||
DisableSSH: cfg.Proxy.DisableReverseTunnel,
|
||||
})
|
||||
if err != nil {
|
||||
listener.Close()
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
listeners.web = listeners.mux.TLS()
|
||||
listeners.reverseTunnel = listeners.mux.SSH()
|
||||
go listeners.mux.Serve()
|
||||
return &listeners, nil
|
||||
case cfg.Proxy.EnableProxyProtocol && !cfg.Proxy.DisableWebService:
|
||||
log.Debugf("Setup Proxy: Proxy protocol is enabled for web service, multiplexing is on.")
|
||||
listener, err := net.Listen("tcp", cfg.Proxy.WebAddr.Addr)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
listeners.mux, err = multiplexer.New(multiplexer.Config{
|
||||
EnableProxyProtocol: cfg.Proxy.EnableProxyProtocol,
|
||||
Listener: listener,
|
||||
DisableTLS: false,
|
||||
DisableSSH: true,
|
||||
})
|
||||
if err != nil {
|
||||
listener.Close()
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
listeners.web = listeners.mux.TLS()
|
||||
listeners.reverseTunnel, err = net.Listen("tcp", cfg.Proxy.ReverseTunnelListenAddr.Addr)
|
||||
if err != nil {
|
||||
listener.Close()
|
||||
listeners.Close()
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
go listeners.mux.Serve()
|
||||
return &listeners, nil
|
||||
default:
|
||||
log.Debugf("Proxy reverse tunnel are listening on the separate ports")
|
||||
if !cfg.Proxy.DisableReverseTunnel {
|
||||
listeners.reverseTunnel, err = net.Listen("tcp", cfg.Proxy.ReverseTunnelListenAddr.Addr)
|
||||
if err != nil {
|
||||
listeners.Close()
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
if !cfg.Proxy.DisableWebService {
|
||||
listeners.web, err = net.Listen("tcp", cfg.Proxy.WebAddr.Addr)
|
||||
if err != nil {
|
||||
listeners.Close()
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
return &listeners, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error {
|
||||
var (
|
||||
askedToExit = true
|
||||
|
@ -836,32 +929,111 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error {
|
|||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
tsrv, err := reversetunnel.NewServer(
|
||||
reversetunnel.Config{
|
||||
ID: process.Config.HostUUID,
|
||||
ClusterName: conn.Identity.Cert.Extensions[utils.CertExtensionAuthority],
|
||||
ClientTLS: tlsConfig,
|
||||
ListenAddr: cfg.Proxy.ReverseTunnelListenAddr,
|
||||
HostSigners: []ssh.Signer{conn.Identity.KeySigner},
|
||||
LocalAuthClient: conn.Client,
|
||||
LocalAccessPoint: accessPoint,
|
||||
NewCachingAccessPoint: process.newLocalCache,
|
||||
Limiter: reverseTunnelLimiter,
|
||||
DirectClusters: []reversetunnel.DirectCluster{
|
||||
{
|
||||
Name: conn.Identity.Cert.Extensions[utils.CertExtensionAuthority],
|
||||
Client: conn.Client,
|
||||
},
|
||||
},
|
||||
Ciphers: cfg.Ciphers,
|
||||
KEXAlgorithms: cfg.KEXAlgorithms,
|
||||
MACAlgorithms: cfg.MACAlgorithms,
|
||||
})
|
||||
listeners, err := process.setupProxyListeners()
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
SSHProxy, err := regular.New(cfg.Proxy.SSHAddr,
|
||||
// Register reverse tunnel agents pool
|
||||
agentPool, err := reversetunnel.NewAgentPool(reversetunnel.AgentPoolConfig{
|
||||
HostUUID: conn.Identity.ID.HostUUID,
|
||||
Client: conn.Client,
|
||||
AccessPoint: accessPoint,
|
||||
HostSigners: []ssh.Signer{conn.Identity.KeySigner},
|
||||
Cluster: conn.Identity.Cert.Extensions[utils.CertExtensionAuthority],
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
// register SSH reverse tunnel server that accepts connections
|
||||
// from remote teleport nodes
|
||||
var tsrv reversetunnel.Server
|
||||
if !process.Config.Proxy.DisableReverseTunnel {
|
||||
tsrv, err = reversetunnel.NewServer(
|
||||
reversetunnel.Config{
|
||||
ID: process.Config.HostUUID,
|
||||
ClusterName: conn.Identity.Cert.Extensions[utils.CertExtensionAuthority],
|
||||
ClientTLS: tlsConfig,
|
||||
Listener: listeners.reverseTunnel,
|
||||
HostSigners: []ssh.Signer{conn.Identity.KeySigner},
|
||||
LocalAuthClient: conn.Client,
|
||||
LocalAccessPoint: accessPoint,
|
||||
NewCachingAccessPoint: process.newLocalCache,
|
||||
Limiter: reverseTunnelLimiter,
|
||||
DirectClusters: []reversetunnel.DirectCluster{
|
||||
{
|
||||
Name: conn.Identity.Cert.Extensions[utils.CertExtensionAuthority],
|
||||
Client: conn.Client,
|
||||
},
|
||||
},
|
||||
Ciphers: cfg.Ciphers,
|
||||
KEXAlgorithms: cfg.KEXAlgorithms,
|
||||
MACAlgorithms: cfg.MACAlgorithms,
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
process.RegisterFunc("proxy.reveresetunnel.server", func() error {
|
||||
utils.Consolef(cfg.Console, "Starting reverse tunnel service is starting on %v using %v", cfg.Proxy.ReverseTunnelListenAddr.Addr, process.Config.CachePolicy)
|
||||
if err := tsrv.Start(); err != nil {
|
||||
utils.Consolef(cfg.Console, "Error: %v", err)
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
// notify parties that we've started reverse tunnel server
|
||||
process.BroadcastEvent(Event{Name: ProxyReverseTunnelServerEvent, Payload: tsrv})
|
||||
tsrv.Wait()
|
||||
if askedToExit {
|
||||
log.Infof("Reverse tunnel exited.")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Register web proxy server
|
||||
if !process.Config.Proxy.DisableWebService {
|
||||
process.RegisterFunc("proxy.web", func() error {
|
||||
utils.Consolef(cfg.Console, "Web proxy service is starting on %v.", cfg.Proxy.WebAddr.Addr)
|
||||
webHandler, err := web.NewHandler(
|
||||
web.Config{
|
||||
Proxy: tsrv,
|
||||
AuthServers: cfg.AuthServers[0],
|
||||
DomainName: cfg.Hostname,
|
||||
ProxyClient: conn.Client,
|
||||
DisableUI: process.Config.Proxy.DisableWebInterface,
|
||||
ProxySSHAddr: cfg.Proxy.SSHAddr,
|
||||
ProxyWebAddr: cfg.Proxy.WebAddr,
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
defer webHandler.Close()
|
||||
|
||||
proxyLimiter.WrapHandle(webHandler)
|
||||
process.BroadcastEvent(Event{Name: ProxyWebServerEvent, Payload: webHandler})
|
||||
|
||||
if !process.Config.Proxy.DisableTLS {
|
||||
tlsConfig, err := utils.CreateTLSConfiguration(cfg.Proxy.TLSCert, cfg.Proxy.TLSKey)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
listeners.web = tls.NewListener(listeners.web, tlsConfig)
|
||||
}
|
||||
if err = http.Serve(listeners.web, proxyLimiter); err != nil {
|
||||
if askedToExit {
|
||||
log.Infof("Proxy web server exited.")
|
||||
return nil
|
||||
}
|
||||
log.Error(err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
} else {
|
||||
log.Infof("Web UI is disabled.")
|
||||
}
|
||||
|
||||
// Register SSH proxy server - SSH jumphost proxy server
|
||||
sshProxy, err := regular.New(cfg.Proxy.SSHAddr,
|
||||
cfg.Hostname,
|
||||
[]ssh.Signer{conn.Identity.KeySigner},
|
||||
accessPoint,
|
||||
|
@ -881,94 +1053,9 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error {
|
|||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
// Register reverse tunnel agents pool
|
||||
agentPool, err := reversetunnel.NewAgentPool(reversetunnel.AgentPoolConfig{
|
||||
HostUUID: conn.Identity.ID.HostUUID,
|
||||
Client: conn.Client,
|
||||
AccessPoint: accessPoint,
|
||||
HostSigners: []ssh.Signer{conn.Identity.KeySigner},
|
||||
Cluster: conn.Identity.Cert.Extensions[utils.CertExtensionAuthority],
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
// register SSH reverse tunnel server that accepts connections
|
||||
// from remote teleport nodes
|
||||
if !process.Config.Proxy.DisableReverseTunnel {
|
||||
process.RegisterFunc("proxy.reveresetunnel.server", func() error {
|
||||
utils.Consolef(cfg.Console, "[PROXY] Reverse tunnel service is starting on %v using %v", cfg.Proxy.ReverseTunnelListenAddr.Addr, process.Config.CachePolicy)
|
||||
if err := tsrv.Start(); err != nil {
|
||||
utils.Consolef(cfg.Console, "[PROXY] Error: %v", err)
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
// notify parties that we've started reverse tunnel server
|
||||
process.BroadcastEvent(Event{Name: ProxyReverseTunnelServerEvent, Payload: tsrv})
|
||||
tsrv.Wait()
|
||||
if askedToExit {
|
||||
log.Infof("Reverse tunnel exited.")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// Register web proxy server
|
||||
var webListener net.Listener
|
||||
if !process.Config.Proxy.DisableWebService {
|
||||
process.RegisterFunc("proxy.web", func() error {
|
||||
utils.Consolef(cfg.Console, "[PROXY] Web proxy service is starting on %v", cfg.Proxy.WebAddr.Addr)
|
||||
webHandler, err := web.NewHandler(
|
||||
web.Config{
|
||||
Proxy: tsrv,
|
||||
AuthServers: cfg.AuthServers[0],
|
||||
DomainName: cfg.Hostname,
|
||||
ProxyClient: conn.Client,
|
||||
DisableUI: process.Config.Proxy.DisableWebInterface,
|
||||
ProxySSHAddr: cfg.Proxy.SSHAddr,
|
||||
ProxyWebAddr: cfg.Proxy.WebAddr,
|
||||
})
|
||||
if err != nil {
|
||||
utils.Consolef(cfg.Console, "[PROXY] starting the web server: %v", err)
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
defer webHandler.Close()
|
||||
|
||||
proxyLimiter.WrapHandle(webHandler)
|
||||
process.BroadcastEvent(Event{Name: ProxyWebServerEvent, Payload: webHandler})
|
||||
|
||||
log.Infof("init TLS listeners")
|
||||
if !process.Config.Proxy.DisableTLS {
|
||||
webListener, err = utils.ListenTLS(
|
||||
cfg.Proxy.WebAddr.Addr,
|
||||
cfg.Proxy.TLSCert,
|
||||
cfg.Proxy.TLSKey)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
} else {
|
||||
|
||||
webListener, err = net.Listen("tcp", cfg.Proxy.WebAddr.Addr)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
if err = http.Serve(webListener, proxyLimiter); err != nil {
|
||||
if askedToExit {
|
||||
log.Infof("Proxy web server exited.")
|
||||
return nil
|
||||
}
|
||||
log.Error(err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
} else {
|
||||
log.Infof("Web UI is disabled.")
|
||||
}
|
||||
|
||||
// Register ssh proxy server
|
||||
process.RegisterFunc("proxy.ssh", func() error {
|
||||
utils.Consolef(cfg.Console, "[PROXY] SSH proxy service is starting on %v", cfg.Proxy.SSHAddr.Addr)
|
||||
if err := SSHProxy.Start(); err != nil {
|
||||
if err := sshProxy.Start(); err != nil {
|
||||
if askedToExit {
|
||||
log.Infof("SSH proxy exited")
|
||||
return nil
|
||||
|
@ -991,12 +1078,12 @@ func (process *TeleportProcess) initProxyEndpoint(conn *Connector) error {
|
|||
|
||||
// execute this when process is asked to exit:
|
||||
process.onExit(func(payload interface{}) {
|
||||
tsrv.Close()
|
||||
SSHProxy.Close()
|
||||
agentPool.Stop()
|
||||
if webListener != nil {
|
||||
webListener.Close()
|
||||
listeners.Close()
|
||||
if tsrv != nil {
|
||||
tsrv.Close()
|
||||
}
|
||||
sshProxy.Close()
|
||||
agentPool.Stop()
|
||||
log.Infof("Proxy service exited.")
|
||||
})
|
||||
return nil
|
||||
|
|
|
@ -260,6 +260,11 @@ func (c *TrustedClusterV2) CheckAndSetDefaults() error {
|
|||
},
|
||||
}
|
||||
}
|
||||
// Imply that by default proxy listens on the same port for
|
||||
// web and reverse tunnel connections
|
||||
if c.Spec.ReverseTunnelAddress == "" {
|
||||
c.Spec.ReverseTunnelAddress = c.Spec.ProxyAddress
|
||||
}
|
||||
if err := c.Spec.RoleMap.Check(); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
|
|
@ -444,6 +444,14 @@ func (s *SrvSuite) testClient(c *C, proxyAddr, targetAddr, remoteAddr string, ss
|
|||
c.Assert(string(out), Equals, "hello\n")
|
||||
}
|
||||
|
||||
func mustListen(a utils.NetAddr) net.Listener {
|
||||
l, err := net.Listen("tcp", a.Addr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (s *SrvSuite) TestProxyReverseTunnel(c *C) {
|
||||
log.Infof("[TEST START] TestProxyReverseTunnel")
|
||||
|
||||
|
@ -453,7 +461,7 @@ func (s *SrvSuite) TestProxyReverseTunnel(c *C) {
|
|||
ClientTLS: s.proxyClient.TLSConfig(),
|
||||
ID: hostID,
|
||||
ClusterName: s.server.ClusterName(),
|
||||
ListenAddr: reverseTunnelAddress,
|
||||
Listener: mustListen(reverseTunnelAddress),
|
||||
HostSigners: []ssh.Signer{s.signer},
|
||||
LocalAuthClient: s.proxyClient,
|
||||
LocalAccessPoint: s.proxyClient,
|
||||
|
@ -622,7 +630,7 @@ func (s *SrvSuite) TestProxyRoundRobin(c *C) {
|
|||
ClusterName: s.server.ClusterName(),
|
||||
ClientTLS: s.proxyClient.TLSConfig(),
|
||||
ID: hostID,
|
||||
ListenAddr: reverseTunnelAddress,
|
||||
Listener: mustListen(reverseTunnelAddress),
|
||||
HostSigners: []ssh.Signer{s.signer},
|
||||
LocalAuthClient: s.proxyClient,
|
||||
LocalAccessPoint: s.proxyClient,
|
||||
|
@ -722,7 +730,7 @@ func (s *SrvSuite) TestProxyDirectAccess(c *C) {
|
|||
ClientTLS: s.proxyClient.TLSConfig(),
|
||||
ID: hostID,
|
||||
ClusterName: s.server.ClusterName(),
|
||||
ListenAddr: reverseTunnelAddress,
|
||||
Listener: mustListen(reverseTunnelAddress),
|
||||
HostSigners: []ssh.Signer{s.signer},
|
||||
LocalAuthClient: s.proxyClient,
|
||||
LocalAccessPoint: s.proxyClient,
|
||||
|
|
|
@ -184,12 +184,12 @@ func (s *WebSuite) SetUpTest(c *C) {
|
|||
s.proxyClient, err = s.server.NewClient(auth.TestBuiltin(teleport.RoleProxy))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
revTunListener, err := net.Listen("tcp", fmt.Sprintf("%v:0", s.server.ClusterName()))
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
revTunServer, err := reversetunnel.NewServer(reversetunnel.Config{
|
||||
ID: node.ID(),
|
||||
ListenAddr: utils.NetAddr{
|
||||
AddrNetwork: "tcp",
|
||||
Addr: fmt.Sprintf("%v:0", s.server.ClusterName()),
|
||||
},
|
||||
ID: node.ID(),
|
||||
Listener: revTunListener,
|
||||
ClientTLS: s.proxyClient.TLSConfig(),
|
||||
ClusterName: s.server.ClusterName(),
|
||||
HostSigners: []ssh.Signer{signer},
|
||||
|
|
Loading…
Reference in a new issue