teleport connect and remote authorities

* teleport connect provides tunnel between local cluster and remote lens
* teleport can optionally trust remote authorities and allow signed keys in
This commit is contained in:
klizhentas 2015-06-22 14:05:15 -07:00
parent 5ab374398c
commit f59223256c
33 changed files with 1401 additions and 73 deletions

View file

@ -46,8 +46,7 @@ cover-package-with-etcd: remove-temp-files
${ETCD_FLAGS} go test -v ./$(p) -coverprofile=/tmp/coverage.out
go tool cover -html=/tmp/coverage.out
run-auth:
go install github.com/gravitational/teleport/teleport
run-auth: install
rm -f /tmp/teleport.auth.sock
teleport -auth\
-authBackend=bolt\
@ -58,8 +57,7 @@ run-auth:
-dataDir=/tmp\
-fqdn=auth.gravitational.io
run-ssh:
go install github.com/gravitational/teleport/teleport
run-ssh: install
tctl token generate --output=/tmp/token -fqdn=node1.gravitational.io
teleport -ssh\
-log=console\
@ -69,8 +67,7 @@ run-ssh:
-sshToken=/tmp/token\
-authServer=tcp://auth.gravitational.io:33000
run-cp: install-assets
go install github.com/gravitational/teleport/teleport
run-cp: install install-assets
teleport -cp\
-cpDomain=gravitational.io\
-log=console\
@ -79,6 +76,34 @@ run-cp: install-assets
-fqdn=node2.gravitational.io\
-authServer=tcp://auth.gravitational.io:33000
run-tun: install
tctl token generate --output=/tmp/token -fqdn=node1.gravitational.io
teleport -tun\
-log=console\
-logSeverity=INFO\
-dataDir=/tmp\
-fqdn=node1.gravitational.io\
-tunToken=/tmp/token\
-tunSrvAddr=tcp://lens.gravitational.io:34000\
-authServer=tcp://auth.gravitational.io:33000
run-embedded: install
rm -f /tmp/teleport.auth.sock
teleport -auth\
-authBackend=bolt\
-authBackendConfig='{"path": "/tmp/teleport.auth.db"}'\
-authDomain=gravitational.io\
-log=console\
-logSeverity=INFO\
-dataDir=/tmp\
-fqdn=auth.gravitational.io\
-ssh\
-authServer=tcp://auth.gravitational.io:33000\
-tun\
-tunSrvAddr=tcp://lens.vendor.io:34000
profile:
go tool pprof http://localhost:6060/debug/pprof/profile

View file

@ -26,4 +26,7 @@ type AccessPoint interface {
// GetWebSessionsKeys returns a list of generated public keys
// associated with user web session
GetWebSessionsKeys(user string) ([]backend.AuthorizedKey, error)
// GetRemoteCerts returns a list of trusted remote certificates
GetRemoteCerts(ctype, fqdn string) ([]backend.RemoteCert, error)
}

View file

@ -101,6 +101,18 @@ func (s *AuthServer) GenerateKeyPair(pass string) ([]byte, []byte, error) {
return s.a.GenerateKeyPair(pass)
}
func (s *AuthServer) UpsertRemoteCert(cert backend.RemoteCert, ttl time.Duration) error {
return s.b.UpsertRemoteCert(cert, ttl)
}
func (s *AuthServer) GetRemoteCerts(ctype string, fqdn string) ([]backend.RemoteCert, error) {
return s.b.GetRemoteCerts(ctype, fqdn)
}
func (s *AuthServer) DeleteRemoteCert(ctype string, fqdn, id string) error {
return s.b.DeleteRemoteCert(ctype, fqdn, id)
}
// ResetHostCA generates host certificate authority and updates the backend
func (s *AuthServer) ResetHostCA(pass string) error {
priv, pub, err := s.a.GenerateKeyPair(pass)

View file

@ -35,7 +35,12 @@ func NewClientFromNetAddr(
return net.Dial(a.Network, a.Addr)
}}}
params = append(params, roundtrip.HTTPClient(client))
return NewClient("http://placeholder:0", params...)
u := url.URL{
Scheme: "http",
Host: "placeholder",
Path: a.Path,
}
return NewClient(u.String(), params...)
}
func NewClient(addr string, params ...roundtrip.ClientParam) (*Client, error) {
@ -80,6 +85,41 @@ func (c *Client) Delete(u string) (*roundtrip.Response, error) {
return c.convertResponse(c.Client.Delete(u))
}
func (c *Client) UpsertRemoteCert(cert backend.RemoteCert, ttl time.Duration) error {
out, err := c.PostForm(c.Endpoint("ca", "remote", cert.Type, "hosts", cert.FQDN), url.Values{
"key": []string{string(cert.Value)},
"ttl": []string{ttl.String()},
"id": []string{cert.ID},
})
if err != nil {
return err
}
var re *remoteCertResponse
if err := json.Unmarshal(out.Bytes(), &re); err != nil {
return err
}
return nil
}
func (c *Client) GetRemoteCerts(ctype string, fqdn string) ([]backend.RemoteCert, error) {
out, err := c.Get(c.Endpoint("ca", "remote", ctype), url.Values{
"fqdn": []string{fqdn},
})
if err != nil {
return nil, err
}
var re *remoteCertsResponse
if err := json.Unmarshal(out.Bytes(), &re); err != nil {
return nil, err
}
return re.RemoteCerts, nil
}
func (c *Client) DeleteRemoteCert(ctype string, fqdn, id string) error {
_, err := c.Delete(c.Endpoint("ca", "remote", ctype, "hosts", fqdn, id))
return err
}
// GenerateToken creates a special provisioning token for the SSH server
// with the specified fqdn that is valid for ttl period seconds.
//

View file

@ -128,7 +128,7 @@ func ReadKeys(fqdn, dataDir string) (ssh.Signer, error) {
return nil, err
}
return sshutils.NewHostSigner(key, cert)
return sshutils.NewSigner(key, cert)
}
func HaveKeys(fqdn, dataDir string) (bool, error) {

View file

@ -59,6 +59,11 @@ func NewAPIServer(s *AuthServer, elog memlog.Logger) *APIServer {
// Generating keypairs
srv.POST("/v1/keypair", srv.generateKeyPair)
// Operations on remote authorities we trust
srv.POST("/v1/ca/remote/:type/hosts/:fqdn", srv.upsertRemoteCert)
srv.DELETE("/v1/ca/remote/:type/hosts/:fqdn/:id", srv.deleteRemoteCert)
srv.GET("/v1/ca/remote/:type", srv.getRemoteCerts)
// Passwords and sessions
srv.POST("/v1/users/:user/web/password", srv.upsertPassword)
srv.POST("/v1/users/:user/web/password/check", srv.checkPassword)
@ -514,6 +519,51 @@ func reply(w http.ResponseWriter, code int, message interface{}) {
w.Write(out)
}
func (s *APIServer) upsertRemoteCert(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
var id, key string
var ttl time.Duration
ctype, fqdn := p[0].Value, p[1].Value
err := form.Parse(r,
form.String("key", &key, form.Required()),
form.String("id", &id, form.Required()),
form.Duration("ttl", &ttl))
if err != nil {
replyErr(w, err)
return
}
cert := backend.RemoteCert{ID: id, Value: []byte(key), FQDN: fqdn, Type: ctype}
if err := s.s.UpsertRemoteCert(cert, ttl); err != nil {
replyErr(w, err)
return
}
reply(w, http.StatusOK, remoteCertResponse{RemoteCert: cert})
}
func (s *APIServer) getRemoteCerts(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
fqdn := r.URL.Query().Get("fqdn")
ctype := p[0].Value
certs, err := s.s.GetRemoteCerts(ctype, fqdn)
if err != nil {
fmt.Printf("error: %v", err)
log.Infof("error: %v", err)
replyErr(w, err)
return
}
reply(w, http.StatusOK, &remoteCertsResponse{RemoteCerts: certs})
}
func (s *APIServer) deleteRemoteCert(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
ctype, fqdn, id := p[0].Value, p[1].Value, p[2].Value
if err := s.s.DeleteRemoteCert(ctype, fqdn, id); err != nil {
replyErr(w, err)
return
}
reply(w, http.StatusOK, message(fmt.Sprintf("cert '%v' deleted", id)))
}
type pubKeyResponse struct {
PubKey string `json:"pubkey"`
}
@ -526,6 +576,14 @@ type certResponse struct {
Cert string `json:"cert"`
}
type remoteCertResponse struct {
RemoteCert backend.RemoteCert `hson:"remote_cert"`
}
type remoteCertsResponse struct {
RemoteCerts []backend.RemoteCert `hson:"remote_certs"`
}
type usersResponse struct {
Users []string `json:"users"`
}

View file

@ -254,3 +254,25 @@ func (s *APISuite) TestTokens(c *C) {
c.Assert(err, IsNil)
c.Assert(len(out), Not(Equals), 0)
}
func (s *APISuite) TestRemoteCACRUD(c *C) {
key := backend.RemoteCert{
FQDN: "example.com",
ID: "id",
Value: []byte("hello1"),
Type: backend.UserCert,
}
err := s.clt.UpsertRemoteCert(key, 0)
c.Assert(err, IsNil)
certs, err := s.clt.GetRemoteCerts(key.Type, key.FQDN)
c.Assert(err, IsNil)
c.Assert(certs[0], DeepEquals, key)
err = s.clt.DeleteRemoteCert(key.Type, key.FQDN, key.ID)
c.Assert(err, IsNil)
err = s.clt.DeleteRemoteCert(key.Type, key.FQDN, key.ID)
c.Assert(err, FitsTypeOf, &backend.NotFoundError{})
}

View file

@ -395,7 +395,7 @@ func NewWebPasswordAuth(user string, password []byte) ([]ssh.AuthMethod, error)
}
func NewHostAuth(key, cert []byte) ([]ssh.AuthMethod, error) {
signer, err := sshutils.NewHostSigner(key, cert)
signer, err := sshutils.NewSigner(key, cert)
if err != nil {
return nil, err
}
@ -433,6 +433,12 @@ func (c *TunClient) Close() error {
return c.dialer.Close()
}
func (c *TunClient) GetDialer() AccessPointDialer {
return func() (net.Conn, error) {
return c.dialer.Dial(c.dialer.addr.Network, "accesspoint:0")
}
}
type TunDialer struct {
sync.Mutex
auth []ssh.AuthMethod
@ -502,3 +508,6 @@ const (
AuthWebSession = "session"
AuthToken = "provision-token"
)
// AccessPointDialer dials to auth access point remote HTTP api
type AccessPointDialer func() (net.Conn, error)

View file

@ -58,7 +58,7 @@ func (s *TunSuite) SetUpTest(c *C) {
hcert, err := s.a.GenerateHostCert(hpub, "localhost", "localhost", 0)
c.Assert(err, IsNil)
signer, err := sshutils.NewHostSigner(hpriv, hcert)
signer, err := sshutils.NewSigner(hpriv, hcert)
c.Assert(err, IsNil)
s.signer = signer
u, err := url.Parse(s.srv.URL)

View file

@ -22,6 +22,11 @@ type Backend interface {
// GetUserCAPub returns the user certificate authority public key
GetUserCAPub() ([]byte, error)
// Remote Certificate management
UpsertRemoteCert(RemoteCert, time.Duration) error
GetRemoteCerts(ctype string, fqdn string) ([]RemoteCert, error)
DeleteRemoteCert(ctype string, fqdn, id string) error
// GetCA returns private, public key and certificate for user CA
GetUserCA() (*CA, error)
@ -185,3 +190,15 @@ type BadParameterError struct {
func (m *BadParameterError) Error() string {
return fmt.Sprintf("bad parameter '%v', %v", m.Param, m.Err)
}
type RemoteCert struct {
Type string
ID string
FQDN string
Value []byte
}
const (
HostCert = "host"
UserCert = "user"
)

View file

@ -62,6 +62,56 @@ func (b *BoltBackend) Close() error {
return nil
}
func (b *BoltBackend) UpsertRemoteCert(cert backend.RemoteCert, ttl time.Duration) error {
return b.upsertKey([]string{"certs", cert.Type, "hosts", cert.FQDN}, cert.ID, cert.Value)
}
func (b *BoltBackend) GetRemoteCerts(ctype string, fqdn string) ([]backend.RemoteCert, error) {
out := []backend.RemoteCert{}
err := b.db.View(func(tx *bolt.Tx) error {
hosts := []string{}
if fqdn == "" {
bkt, err := getBucket(tx, []string{"certs", ctype, "hosts"})
if err != nil {
if isNotFound(err) {
return nil
}
return err
}
c := bkt.Cursor()
for k, _ := c.First(); k != nil; k, _ = c.Next() {
hosts = append(hosts, string(k))
}
} else {
hosts = []string{fqdn}
}
for _, h := range hosts {
bkt, err := getBucket(tx, []string{"certs", ctype, "hosts", h})
if err != nil {
return err
}
c := bkt.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
out = append(out, backend.RemoteCert{
Type: ctype,
FQDN: h,
ID: string(k),
Value: v,
})
}
}
return nil
})
if err != nil {
return nil, err
}
return out, nil
}
func (b *BoltBackend) DeleteRemoteCert(ctype string, fqdn, id string) error {
return b.deleteKey([]string{"certs", ctype, "hosts", fqdn}, id)
}
// GetUsers returns a list of users registered in the backend
func (b *BoltBackend) GetUsers() ([]string, error) {
out := []string{}
@ -423,3 +473,8 @@ func getBucket(b *bolt.Tx, buckets []string) (*bolt.Bucket, error) {
}
return bkt, nil
}
func isNotFound(err error) bool {
_, ok := err.(*backend.NotFoundError)
return ok
}

View file

@ -73,3 +73,7 @@ func (s *BoltSuite) TestLocking(c *C) {
func (s *BoltSuite) TestToken(c *C) {
s.suite.TokenCRUD(c)
}
func (s *BoltSuite) TestRemoteCert(c *C) {
s.suite.RemoteCertCRUD(c)
}

View file

@ -113,6 +113,78 @@ func (b *bk) GetHostCAPub() ([]byte, error) {
return ca.Pub, nil
}
func (b *bk) UpsertRemoteCert(cert backend.RemoteCert, ttl time.Duration) error {
_, err := b.client.Set(b.key("certs", cert.Type, "hosts", cert.FQDN, cert.ID), string(cert.Value), uint64(ttl/time.Second))
return convertErr(err)
}
func (b *bk) getKeys(key string) ([]string, error) {
vals := []string{}
re, err := b.client.Get(key, true, false)
if err != nil {
if notFound(err) {
return vals, nil
}
return nil, convertErr(err)
}
if !isDir(re.Node) {
return nil, fmt.Errorf("expected directory")
}
for _, n := range re.Node.Nodes {
vals = append(vals, suffix(n.Key))
}
return vals, nil
}
func (b *bk) GetRemoteCerts(ctype, fqdn string) ([]backend.RemoteCert, error) {
certs := []backend.RemoteCert{}
if ctype == "" {
return nil, fmt.Errorf("provide certificate type")
}
var hosts []string
var err error
if fqdn != "" {
hosts = []string{fqdn}
} else {
if hosts, err = b.getKeys(b.key("certs", ctype, "hosts")); err != nil {
return nil, err
}
}
// for each host, get a list of ids
hs := make(map[string][]string)
for _, h := range hosts {
vals, err := b.getKeys(b.key("certs", ctype, "hosts", h))
if err != nil {
return nil, err
}
hs[h] = vals
}
// now, for each id retrieve it's value
for h, ids := range hs {
for _, id := range ids {
re, err := b.client.Get(b.key("certs", ctype, "hosts", h), true, true)
if err != nil {
return nil, convertErr(err)
}
cert := backend.RemoteCert{
Value: []byte(re.Node.Nodes[0].Value),
Type: ctype,
FQDN: h,
ID: id,
}
certs = append(certs, cert)
}
}
return certs, nil
}
func (b *bk) DeleteRemoteCert(ctype, fqdn, id string) error {
_, err := b.client.Delete(b.key("certs", ctype, "hosts", fqdn, id), true)
return convertErr(err)
}
// GetUsers returns a list of users registered in the backend
func (b *bk) GetUsers() ([]string, error) {
values := []string{}

View file

@ -117,3 +117,7 @@ func (s *EtcdSuite) TestLocking(c *C) {
func (s *EtcdSuite) TestToken(c *C) {
s.suite.TokenCRUD(c)
}
func (s *EtcdSuite) TestRemoteCert(c *C) {
s.suite.RemoteCertCRUD(c)
}

View file

@ -20,7 +20,8 @@ type MemBackend struct {
WebTuns map[string]backend.WebTun
Tokens map[string]string
Locks map[string]time.Time
Locks map[string]time.Time
RemoteCerts []backend.RemoteCert
}
type User struct {
@ -71,6 +72,42 @@ func (b *MemBackend) Close() error {
return nil
}
// Remote Certificate management
func (b *MemBackend) UpsertRemoteCert(crt backend.RemoteCert, ttl time.Duration) error {
for i, c := range b.RemoteCerts {
if c.ID == crt.ID && c.FQDN == crt.FQDN && c.Type == crt.Type {
b.RemoteCerts[i] = crt
return nil
}
}
b.RemoteCerts = append(b.RemoteCerts, crt)
return nil
}
func (b *MemBackend) GetRemoteCerts(ctype string, fqdn string) ([]backend.RemoteCert, error) {
out := []backend.RemoteCert{}
for _, c := range b.RemoteCerts {
if c.Type != ctype {
continue
}
if fqdn != "" && fqdn != c.FQDN {
continue
}
out = append(out, c)
}
return out, nil
}
func (b *MemBackend) DeleteRemoteCert(ctype string, fqdn, id string) error {
for i, c := range b.RemoteCerts {
if c.ID == id && c.FQDN == fqdn && c.Type == ctype {
b.RemoteCerts = append(b.RemoteCerts[:i], b.RemoteCerts[i+1:]...)
return nil
}
}
return &backend.NotFoundError{}
}
// GetUsers returns a list of users registered in the backend
func (b *MemBackend) GetUsers() ([]string, error) {
out := []string{}

View file

@ -68,3 +68,7 @@ func (s *MemSuite) TestLocking(c *C) {
func (s *MemSuite) TestToken(c *C) {
s.suite.TokenCRUD(c)
}
func (s *MemSuite) TestRemoteCert(c *C) {
s.suite.RemoteCertCRUD(c)
}

View file

@ -236,6 +236,61 @@ func (s *BackendSuite) TokenCRUD(c *C) {
c.Assert(err, FitsTypeOf, &backend.NotFoundError{})
}
func (s *BackendSuite) RemoteCertCRUD(c *C) {
out, err := s.B.GetRemoteCerts(backend.HostCert, "")
c.Assert(err, IsNil)
c.Assert(out, DeepEquals, []backend.RemoteCert{})
ca := backend.RemoteCert{
Type: backend.HostCert,
ID: "c1",
FQDN: "example.com",
Value: []byte("hello"),
}
c.Assert(s.B.UpsertRemoteCert(ca, 0), IsNil)
out, err = s.B.GetRemoteCerts(backend.HostCert, ca.FQDN)
c.Assert(err, IsNil)
c.Assert(out[0], DeepEquals, ca)
ca2 := backend.RemoteCert{
Type: backend.HostCert,
ID: "c2",
FQDN: "example.org",
Value: []byte("hello2"),
}
c.Assert(s.B.UpsertRemoteCert(ca2, 0), IsNil)
out, err = s.B.GetRemoteCerts(backend.HostCert, ca2.FQDN)
c.Assert(err, IsNil)
c.Assert(out[0], DeepEquals, ca2)
out, err = s.B.GetRemoteCerts(backend.HostCert, "")
c.Assert(err, IsNil)
c.Assert(len(out), Equals, 2)
certs := make(map[string]backend.RemoteCert)
for _, c := range out {
certs[c.FQDN+c.ID] = c
}
c.Assert(certs[ca.FQDN+ca.ID], DeepEquals, ca)
c.Assert(certs[ca2.FQDN+ca2.ID], DeepEquals, ca2)
// Update ca
ca.Value = []byte("hello updated")
c.Assert(s.B.UpsertRemoteCert(ca, 0), IsNil)
out, err = s.B.GetRemoteCerts(backend.HostCert, ca.FQDN)
c.Assert(err, IsNil)
c.Assert(out[0], DeepEquals, ca)
err = s.B.DeleteRemoteCert(backend.HostCert, ca.FQDN, ca.ID)
c.Assert(err, IsNil)
err = s.B.DeleteRemoteCert(backend.HostCert, ca.FQDN, ca.ID)
c.Assert(err, FitsTypeOf, &backend.NotFoundError{})
}
func toSet(vals []string) map[string]struct{} {
out := make(map[string]struct{}, len(vals))
for _, v := range vals {

View file

@ -371,6 +371,7 @@ func executeTemplate(w http.ResponseWriter, name string, data interface{}) {
log.Errorf("Execute template: %v", err)
replyErr(w, http.StatusInternalServerError, fmt.Errorf("internal render error"))
}
}
type ctx struct {

View file

@ -71,12 +71,15 @@ func (*Exec) Schema() string {
return "teleport.exec"
}
func NewShell(shell string, log io.Reader, code int, err error) *Shell {
func NewShell(conn ssh.ConnMetadata, shell string, log io.Reader, code int, err error) *Shell {
return &Shell{
Shell: shell,
Log: collectOutput(log),
Code: code,
Error: errMsg(err),
Shell: shell,
Log: collectOutput(log),
Code: code,
Error: errMsg(err),
User: conn.User(),
LocalAddr: conn.LocalAddr().String(),
RemoteAddr: conn.RemoteAddr().String(),
}
}
@ -93,6 +96,15 @@ type Shell struct {
// Log is a captured session log
Log string `json:"log"`
// User is SSH user
User string `json:"user"`
// LocalAddr local connecting address
LocalAddr string `json:"laddr"`
// RemoteAddr remote connecting address
RemoteAddr string `json:"raddr"`
}
func (*Shell) Schema() string {

View file

@ -2,6 +2,7 @@ package service
import (
"fmt"
"time"
"github.com/gravitational/teleport/auth"
authority "github.com/gravitational/teleport/auth/native"
@ -10,6 +11,7 @@ import (
"github.com/gravitational/teleport/backend/etcdbk"
"github.com/gravitational/teleport/cp"
"github.com/gravitational/teleport/srv"
"github.com/gravitational/teleport/tun"
"github.com/gravitational/teleport/utils"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/codahale/lunk"
@ -53,6 +55,12 @@ func NewTeleport(cfg Config) (*TeleportService, error) {
}
}
if cfg.Tun.Enabled {
if err := initTun(t, cfg); err != nil {
return nil, err
}
}
return t, nil
}
@ -89,7 +97,7 @@ func initAuth(t *TeleportService, cfg Config) error {
return utils.StartHTTPServer(a.HTTPAddr, t)
})
// register SSH endpoint
// register auth SSH-based endpoint
t.RegisterFunc(func() error {
tsrv, err := auth.NewTunServer(
a.SSHAddr, []ssh.Signer{signer},
@ -146,7 +154,7 @@ func initSSH(t *TeleportService, cfg Config) error {
// this means the server has not been initialized yet we are starting
// the registering client that attempts to connect ot the auth server
// and provision the keys
return initSSHRegister(t, cfg)
return initRegister(t, cfg.SSH.Token, cfg)
}
return initSSHEndpoint(t, cfg)
}
@ -186,20 +194,91 @@ func initSSHEndpoint(t *TeleportService, cfg Config) error {
return nil
}
func initSSHRegister(t *TeleportService, cfg Config) error {
func initRegister(t *TeleportService, token string, cfg Config) error {
// we are on the same server as the auth endpoint
// and there's no token. we can handle this
if cfg.Auth.Enabled && token == "" {
log.Infof("registering in embedded mode, connecting to local auth server")
clt, err := auth.NewClientFromNetAddr(cfg.Auth.HTTPAddr)
if err != nil {
log.Errorf("failed to instantiate client: %v", err)
return err
}
token, err = clt.GenerateToken(cfg.FQDN, 30*time.Second)
if err != nil {
log.Errorf("failed to generate token: %v", err)
}
return err
}
t.RegisterFunc(func() error {
log.Infof("teleport:ssh connecting to auth servers %v", cfg.SSH.Token)
log.Infof("teleport:register connecting to auth servers %v", cfg.SSH.Token)
if err := auth.Register(
cfg.FQDN, cfg.DataDir, cfg.SSH.Token, cfg.AuthServers); err != nil {
cfg.FQDN, cfg.DataDir, token, cfg.AuthServers); err != nil {
log.Errorf("teleport:ssh register failed: %v", err)
return err
}
log.Infof("teleport:ssh registered successfully, starting SSH endpoint")
log.Infof("teleport:register registered successfully")
return initSSHEndpoint(t, cfg)
})
return nil
}
func initTun(t *TeleportService, cfg Config) error {
if cfg.DataDir == "" {
return fmt.Errorf("please supply data directory")
}
if len(cfg.AuthServers) == 0 {
return fmt.Errorf("supply at least one auth server")
}
haveKeys, err := auth.HaveKeys(cfg.FQDN, cfg.DataDir)
if err != nil {
return err
}
if !haveKeys {
// this means the server has not been initialized yet we are starting
// the registering client that attempts to connect ot the auth server
// and provision the keys
return initRegister(t, cfg.Tun.Token, cfg)
}
return initTunAgent(t, cfg)
}
func initTunAgent(t *TeleportService, cfg Config) error {
signer, err := auth.ReadKeys(cfg.FQDN, cfg.DataDir)
client, err := auth.NewTunClient(
cfg.AuthServers[0],
cfg.FQDN,
[]ssh.AuthMethod{ssh.PublicKeys(signer)})
elog := &FanOutEventLogger{
Loggers: []lunk.EventLogger{
lunk.NewTextEventLogger(log.GetLogger().Writer(log.SeverityInfo)),
lunk.NewJSONEventLogger(client.GetLogWriter()),
}}
a, err := tun.NewAgent(
cfg.Tun.SrvAddr,
cfg.FQDN,
[]ssh.Signer{signer},
client,
tun.SetEventLogger(elog))
if err != nil {
return err
}
t.RegisterFunc(func() error {
log.Infof("teleport ws agent starting")
if err := a.Start(); err != nil {
log.Fatalf("failed to start: %v", err)
return err
}
a.Wait()
return nil
})
return nil
}
func initBackend(btype, bcfg string) (backend.Backend, error) {
switch btype {
case "etcd":
@ -221,8 +300,8 @@ func initLogging(ltype, severity string) error {
}
func validateConfig(cfg Config) error {
if !cfg.Auth.Enabled && !cfg.SSH.Enabled && !cfg.CP.Enabled {
return fmt.Errorf("supply at least one of Auth, SSH or CP roles")
if !cfg.Auth.Enabled && !cfg.SSH.Enabled && !cfg.CP.Enabled && !cfg.Tun.Enabled {
return fmt.Errorf("supply at least one of Auth, SSH, CP or Tun roles")
}
return nil
}
@ -252,6 +331,7 @@ type Config struct {
SSH SSHConfig
Auth AuthConfig
CP CPConfig
Tun TunConfig
}
type AuthConfig struct {
@ -275,3 +355,9 @@ type CPConfig struct {
Addr utils.NetAddr
Domain string
}
type TunConfig struct {
Token string
Enabled bool
SrvAddr utils.NetAddr
}

View file

@ -6,7 +6,7 @@ import (
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/mailgun/log"
)
// Supervisor implements the simple service logick
// Supervisor implements the simple service logic
type Supervisor struct {
state int
sync.Mutex

View file

@ -98,32 +98,47 @@ func (s *Server) heartbeatPresence() {
}
}
func (s *Server) getUserCAKey() (ssh.PublicKey, error) {
func (s *Server) getTrustedCAKeys() ([]ssh.PublicKey, error) {
out := []ssh.PublicKey{}
authKeys := [][]byte{}
key, err := s.ap.GetUserCAPub()
if err != nil {
return nil, err
}
pubKey, _, _, _, err := ssh.ParseAuthorizedKey(key)
authKeys = append(authKeys, key)
certs, err := s.ap.GetRemoteCerts(backend.UserCert, "")
if err != nil {
return nil, fmt.Errorf("failed to parse CA public key '%v', err: %v",
string(key), err)
return nil, err
}
return pubKey, nil
for _, c := range certs {
authKeys = append(authKeys, c.Value)
}
for _, ak := range authKeys {
pk, _, _, _, err := ssh.ParseAuthorizedKey(ak)
if err != nil {
return nil, fmt.Errorf("failed to parse CA public key '%v', err: %v",
string(ak), err)
}
out = append(out, pk)
}
return out, nil
}
// isAuthority is called during checking the client key, to see if the signing
// key is the real CA authority key.
func (s *Server) isAuthority(auth ssh.PublicKey) bool {
key, err := s.getUserCAKey()
keys, err := s.getTrustedCAKeys()
if err != nil {
log.Errorf("failed to retrieve user authority key, err: %v", err)
log.Errorf("failed to retrieve trused keys, err: %v", err)
return false
}
if !sshutils.KeysEqual(key, auth) {
log.Warningf("authority signature check failed, signing keys mismatch")
return false
for _, k := range keys {
if sshutils.KeysEqual(k, auth) {
return true
}
}
return true
return false
}
// userKeys returns keys registered for a given user in a configuration backend
@ -176,20 +191,22 @@ func (s *Server) keyAuth(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permiss
conn.RemoteAddr(), conn.LocalAddr(), conn.User(), conn.User(), err)
return nil, err
}
keys, err := s.userKeys(conn.User())
if err != nil {
log.Errorf("failed to retrieve user keys: %v", err)
return nil, err
}
for _, k := range keys {
if sshutils.KeysEqual(k, key) {
log.Infof("%v SUCCESS auth", cid)
s.elog.Log(eventID, events.NewAuthAttempt(conn, key, true, nil))
return p, nil
}
}
log.Infof("%v FAIL auth, no matching keys found", cid)
return nil, fmt.Errorf("authentication failed")
return p, nil
/*
TODO(klizhentas) replace this with revocation checking
keys, err := s.userKeys(conn.User())
if err != nil {
log.Errorf("failed to retrieve user keys: %v", err)
return nil, err
}
for _, k := range keys {
if sshutils.KeysEqual(k, key) {
log.Infof("%v SUCCESS auth", cid)
s.elog.Log(eventID, events.NewAuthAttempt(conn, key, true, nil))
return p, nil
}
}
*/
}
// Close closes listening socket and stops accepting connections
@ -336,7 +353,7 @@ func (s *Server) dispatch(sconn *ssh.ServerConn, ch ssh.Channel, req *ssh.Reques
return s.handlePTYReq(ch, req, ctx)
case "shell":
// SSH client asked to launch shell, we allocate PTY and start shell session
return s.handleShell(ch, req, ctx)
return s.handleShell(sconn, ch, req, ctx)
case "env":
// we currently ignore setting any environment variables via SSH for security purposes
return s.handleEnv(ch, req, ctx)
@ -396,7 +413,7 @@ func (s *Server) handleSubsystem(sconn *ssh.ServerConn, ch ssh.Channel, req *ssh
return nil
}
func (s *Server) handleShell(ch ssh.Channel, req *ssh.Request, ctx *ctx) error {
func (s *Server) handleShell(sconn *ssh.ServerConn, ch ssh.Channel, req *ssh.Request, ctx *ctx) error {
log.Infof("%v handleShell()", ctx)
if ctx.getTerm() == nil {
t, err := newTerm()
@ -436,11 +453,11 @@ func (s *Server) handleShell(ch ssh.Channel, req *ssh.Request, ctx *ctx) error {
result, err := collectStatus(cmd, cmd.Wait())
if err != nil {
log.Errorf("%v wait failed: %v", ctx, err)
ctx.emit(events.NewShell(s.shell, out, -1, err))
ctx.emit(events.NewShell(sconn, s.shell, out, -1, err))
}
if result != nil {
log.Infof("%v result collected: %v", ctx, result)
ctx.emit(events.NewShell(s.shell, out, result.code, nil))
ctx.emit(events.NewShell(sconn, s.shell, out, result.code, nil))
ctx.sendResult(*result)
}
}()

View file

@ -55,7 +55,7 @@ func (s *SrvSuite) SetUpTest(c *C) {
// set up user CA and set up a user that has access to the server
c.Assert(s.a.ResetUserCA(""), IsNil)
signer, err := sshutils.NewHostSigner(hpriv, hcert)
signer, err := sshutils.NewSigner(hpriv, hcert)
c.Assert(err, IsNil)
srv, err := New(

View file

@ -6,25 +6,23 @@ import (
"github.com/gravitational/teleport/Godeps/_workspace/src/golang.org/x/crypto/ssh"
)
func NewHostSigner(key, cert []byte) (ssh.Signer, error) {
hostSigner, err := ssh.ParsePrivateKey(key)
func NewSigner(keyBytes, certBytes []byte) (ssh.Signer, error) {
keySigner, err := ssh.ParsePrivateKey(keyBytes)
if err != nil {
return nil, fmt.Errorf("failed to parse host private key, err: %v", err)
}
hostCAKey, _, _, _, err := ssh.ParseAuthorizedKey(cert)
pubkey, _, _, _, err := ssh.ParseAuthorizedKey(certBytes)
if err != nil {
return nil, fmt.Errorf("failed to parse server CA certificate '%v', err: %v", string(cert), err)
return nil, fmt.Errorf(
"failed to parse server CA certificate '%v', err: %v",
string(certBytes), err)
}
hostCert, ok := hostCAKey.(*ssh.Certificate)
cert, ok := pubkey.(*ssh.Certificate)
if !ok {
return nil, fmt.Errorf("expected host CA certificate, got %T ", hostCAKey)
return nil, fmt.Errorf("expected CA certificate, got %T ", pubkey)
}
signer, err := ssh.NewCertSigner(hostCert, hostSigner)
if err != nil {
return nil, fmt.Errorf("failed to create certificate signer, err: %v", err)
}
return signer, nil
return ssh.NewCertSigner(cert, keySigner)
}

View file

@ -7,6 +7,19 @@ import (
"github.com/gravitational/teleport/Godeps/_workspace/src/golang.org/x/crypto/ssh"
)
func NewUpstream(clt *ssh.Client) (*Upstream, error) {
session, err := clt.NewSession()
if err != nil {
clt.Close()
return nil, err
}
return &Upstream{
addr: clt.Conn.RemoteAddr().String(),
client: clt,
session: session,
}, nil
}
func DialUpstream(username, addr string, signers []ssh.Signer) (*Upstream, error) {
sshConfig := &ssh.ClientConfig{
User: username,

View file

@ -3,7 +3,9 @@ package command
import (
"fmt"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/buger/goterm"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/codegangsta/cli"
"github.com/gravitational/teleport/backend"
)
func newHostCACommand(c *Command) cli.Command {
@ -50,6 +52,46 @@ func newUserCACommand(c *Command) cli.Command {
}
}
func newRemoteCACommand(c *Command) cli.Command {
return cli.Command{
Name: "remoteca",
Usage: "Operations with remote certificate authority",
Subcommands: []cli.Command{
{
Name: "upsert",
Usage: "Upsert remote certificate to trust",
Flags: []cli.Flag{
cli.StringFlag{Name: "id", Usage: "Certificate id"},
cli.StringFlag{Name: "fqdn", Usage: "FQDN of the remote party"},
cli.StringFlag{Name: "type", Usage: "Cert type (host or user)"},
cli.StringFlag{Name: "path", Usage: "Cert path (reads from stdout if omitted)"},
cli.DurationFlag{Name: "ttl", Usage: "ttl for certificate to be trusted"},
},
Action: c.upsertRemoteCert,
},
{
Name: "ls",
Usage: "List trusted remote certificates",
Flags: []cli.Flag{
cli.StringFlag{Name: "fqdn", Usage: "FQDN of the remote party"},
cli.StringFlag{Name: "type", Usage: "Cert type (host or user)"},
},
Action: c.getRemoteCerts,
},
{
Name: "rm",
Usage: "Remote remote CA from list of trusted certs",
Flags: []cli.Flag{
cli.StringFlag{Name: "id", Usage: "Certificate id"},
cli.StringFlag{Name: "fqdn", Usage: "FQDN of the remote party"},
cli.StringFlag{Name: "type", Usage: "Cert type (host or user)"},
},
Action: c.deleteRemoteCert,
},
},
}
}
func (cmd *Command) resetHostCA(c *cli.Context) {
if !c.Bool("confirm") && !cmd.confirm("Reseting private and public keys for Host CA. This will invalidate all signed host certs. Continue?") {
cmd.printError(fmt.Errorf("aborted by user"))
@ -93,3 +135,53 @@ func (cmd *Command) getUserCAPub(c *cli.Context) {
cmd.printOK("User CA Key")
fmt.Fprintf(cmd.out, string(key))
}
func (cmd *Command) upsertRemoteCert(c *cli.Context) {
ctype, fqdn, id := c.String("type"), c.String("fqdn"), c.String("id")
val, err := cmd.readInput(c.String("path"))
if err != nil {
cmd.printError(err)
return
}
cert := backend.RemoteCert{
FQDN: fqdn,
Type: ctype,
ID: id,
Value: val,
}
if err := cmd.client.UpsertRemoteCert(cert, c.Duration("ttl")); err != nil {
cmd.printError(err)
return
}
cmd.printOK("Remote cert have been upserted")
}
func (cmd *Command) getRemoteCerts(c *cli.Context) {
certs, err := cmd.client.GetRemoteCerts(c.String("type"), c.String("fqdn"))
if err != nil {
cmd.printError(err)
return
}
fmt.Fprintf(cmd.out, remoteCertsView(certs))
}
func (cmd *Command) deleteRemoteCert(c *cli.Context) {
err := cmd.client.DeleteRemoteCert(c.String("type"), c.String("fqdn"), c.String("id"))
if err != nil {
cmd.printError(err)
return
}
cmd.printOK("certificate deleted")
}
func remoteCertsView(certs []backend.RemoteCert) string {
t := goterm.NewTable(0, 10, 5, ' ', 0)
fmt.Fprint(t, "Type\tFQDN\tID\tValue\n")
if len(certs) == 0 {
return t.String()
}
for _, c := range certs {
fmt.Fprintf(t, "%v\t%v\t%v\t%v\n", c.Type, c.FQDN, c.ID, string(c.Value))
}
return t.String()
}

View file

@ -50,6 +50,7 @@ func (cmd *Command) Run(args []string) error {
app.Commands = []cli.Command{
newHostCACommand(cmd),
newUserCACommand(cmd),
newRemoteCACommand(cmd),
newUserCommand(cmd),
newTokenCommand(cmd),
newSecretCommand(cmd),

View file

@ -115,12 +115,9 @@ func (s *CmdSuite) TestUserCRUD(c *C) {
fkey.Write(pub)
out := s.run("user", "upsert_key", "-user", "alex", "-keyid", "key1", "-key", fkey.Name())
c.Assert(out, Matches, fmt.Sprintf(".*%v.*", "certificate:"))
c.Assert(out, Matches, fmt.Sprintf(".*%v.*", pub))
parts := strings.Split(out, "certificate:")
c.Assert(len(parts), Equals, 2)
c.Assert(trim(string(s.bk.Users["alex"].Keys["key1"].Value)), Equals, trim(parts[1]))
c.Assert(trim(string(s.bk.Users["alex"].Keys["key1"].Value)), Equals, trim(out))
c.Assert(
s.run("user", "ls"),
@ -139,6 +136,30 @@ func (s *CmdSuite) TestGenerateToken(c *C) {
c.Assert(s.asrv.ValidateToken(token, "a.example.com"), IsNil)
}
func (s *CmdSuite) TestRemoteCertCRUD(c *C) {
c.Assert(s.asrv.ResetUserCA(""), IsNil)
_, pub, err := s.asrv.GenerateKeyPair("")
c.Assert(err, IsNil)
fkey, err := ioutil.TempFile("", "teleport")
c.Assert(err, IsNil)
defer fkey.Close()
fkey.Write(pub)
out := s.run("remoteca", "upsert", "-id", "id1", "-type", "user", "-fqdn", "example.com", "-path", fkey.Name())
c.Assert(out, Matches, fmt.Sprintf(".*%v.*", "upserted"))
c.Assert(trim(string(s.bk.RemoteCerts[0].Value)), Equals, trim(string(pub)))
out = s.run("remoteca", "ls", "-type", "user")
c.Assert(out, Matches, fmt.Sprintf(".*%v.*", "example.com"))
out = s.run("remoteca", "rm", "-type", "user", "-fqdn", "example.com", "-id", "id1")
c.Assert(out, Matches, fmt.Sprintf(".*%v.*", "deleted"))
c.Assert(len(s.bk.RemoteCerts), Equals, 0)
}
func trim(val string) string {
return strings.Trim(val, " \t\n")
}

View file

@ -79,7 +79,7 @@ func (cmd *Command) upsertKey(c *cli.Context) {
cmd.printError(err)
return
}
fmt.Fprintf(cmd.out, "certificate:\n%v", string(signed))
fmt.Fprintf(cmd.out, "%v", string(signed))
}
func (cmd *Command) deleteUser(c *cli.Context) {

View file

@ -100,6 +100,21 @@ func main() {
&cfg.CP.Domain, "cpDomain", "",
"control panel domain to serve, e.g. example.com")
// Outbound tunnel role options
flag.BoolVar(&cfg.Tun.Enabled, "tun", false, "enable outbound tunnel")
flag.Var(
utils.NewNetAddrVal(
utils.NetAddr{
Network: "tcp",
Addr: "localhost:33006",
}, &cfg.Tun.SrvAddr),
"tunSrvAddr", "tun agent dial address")
flag.StringVar(
&cfg.Tun.Token, "tunToken", "",
"one time provisioning token for tun agent to register with authority")
flag.Parse()
// some variables can be set via environment variables
@ -108,6 +123,10 @@ func main() {
cfg.SSH.Token = os.Getenv("TELEPORT_SSH_TOKEN")
}
if os.Getenv("TELEPORT_TUN_TOKEN") != "" {
cfg.Tun.Token = os.Getenv("TELEPORT_TUN_TOKEN")
}
srv, err := service.NewTeleport(cfg)
if err != nil {
fmt.Printf("error starting teleport: %v\n", err)

322
tun/agent.go Normal file
View file

@ -0,0 +1,322 @@
package tun
import (
"fmt"
"io"
"net"
"sync"
"time"
"github.com/gravitational/teleport/auth"
"github.com/gravitational/teleport/backend"
"github.com/gravitational/teleport/sshutils"
"github.com/gravitational/teleport/utils"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/codahale/lunk"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/mailgun/log"
"github.com/gravitational/teleport/Godeps/_workspace/src/golang.org/x/crypto/ssh"
)
type Agent struct {
addr utils.NetAddr
elog lunk.EventLogger
clt *auth.TunClient
signers []ssh.Signer
fqdn string
waitC chan bool
disconnectC chan bool
conn ssh.Conn
}
type AgentOption func(a *Agent) error
func SetEventLogger(e lunk.EventLogger) AgentOption {
return func(s *Agent) error {
s.elog = e
return nil
}
}
func NewAgent(addr utils.NetAddr, fqdn string, signers []ssh.Signer,
clt *auth.TunClient, options ...AgentOption) (*Agent, error) {
a := &Agent{
clt: clt,
addr: addr,
fqdn: fqdn,
signers: signers,
waitC: make(chan bool),
disconnectC: make(chan bool, 10),
}
for _, o := range options {
if err := o(a); err != nil {
return nil, err
}
}
if a.elog == nil {
a.elog = utils.NullEventLogger
}
return a, nil
}
func (a *Agent) Start() error {
if err := a.reconnect(); err != nil {
return err
}
go a.handleDisconnect()
return nil
}
func (a *Agent) handleDisconnect() {
log.Infof("will handle disconnects")
for {
select {
case <-a.disconnectC:
log.Infof("detected disconnect, reconnecting")
a.reconnect()
}
}
}
func (a *Agent) reconnect() error {
var err error
for i := 0; i < 10; i++ {
if err = a.connect(); err != nil {
log.Infof("%v connect attempt %v: %v", a, i, err)
time.Sleep(time.Duration(i) * time.Second)
continue
}
return nil
}
return err
}
func (a *Agent) Wait() error {
<-a.waitC
return nil
}
func (a *Agent) String() string {
return fmt.Sprintf("tunagent(remote=%v)", a.addr)
}
func (a *Agent) checkHostSignature(hostport string, remote net.Addr, key ssh.PublicKey) error {
cert, ok := key.(*ssh.Certificate)
if !ok {
log.Infof("expected: %v", key)
return fmt.Errorf("expected certificate")
}
hostname, _, err := net.SplitHostPort(hostport)
if err != nil {
log.Errorf("error spliting hostport(%v), err: %v", hostport, err)
return err
}
certs, err := a.clt.GetRemoteCerts(backend.HostCert, hostname)
if err != nil {
log.Errorf("failed to fetch remote certs: %v", err)
return err
}
for _, c := range certs {
log.Infof("checking key(id=%v) against host %v", c.ID, c.FQDN)
pk, _, _, _, err := ssh.ParseAuthorizedKey(c.Value)
if err != nil {
log.Errorf("error parsing key: %v", err)
return err
}
if sshutils.KeysEqual(pk, cert.SignatureKey) {
log.Infof("matched key %v for %v", c.ID, c.FQDN)
return nil
}
}
return fmt.Errorf("no matching keys found")
}
func (a *Agent) connect() error {
c, err := ssh.Dial(a.addr.Network, a.addr.Addr, &ssh.ClientConfig{
User: a.fqdn,
Auth: []ssh.AuthMethod{ssh.PublicKeys(a.signers...)},
HostKeyCallback: a.checkHostSignature,
})
if err != nil {
log.Errorf("failed to dial: %v", err)
return err
}
a.conn = c
go a.startHeartbeat()
go a.handleAccessPoint(c.HandleChannelOpen(chanAccessPoint))
go a.handleTransport(c.HandleChannelOpen(chanTransport))
log.Infof("%v connection established", a)
return nil
}
func (a *Agent) handleAccessPoint(newC <-chan ssh.NewChannel) {
for {
nch := <-newC
if nch == nil {
log.Infof("connection closed, returning")
return
}
log.Infof("got access point request: %v", nch.ChannelType())
ch, req, err := nch.Accept()
if err != nil {
log.Errorf("failed to accept request: %v", err)
}
go a.proxyAccessPoint(ch, req)
}
}
func (a *Agent) handleTransport(newC <-chan ssh.NewChannel) {
for {
nch := <-newC
if nch == nil {
log.Infof("connection closed, returing")
return
}
log.Infof("got transport request: %v", nch.ChannelType())
ch, req, err := nch.Accept()
if err != nil {
log.Errorf("failed to accept request: %v", err)
}
go a.proxyTransport(ch, req)
}
}
func (a *Agent) proxyAccessPoint(ch ssh.Channel, req <-chan *ssh.Request) {
defer ch.Close()
conn, err := a.clt.GetDialer()()
if err != nil {
log.Errorf("%v error dialing: %v", a, err)
return
}
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
defer conn.Close()
io.Copy(conn, ch)
}()
go func() {
defer wg.Done()
defer conn.Close()
io.Copy(ch, conn)
}()
wg.Wait()
}
func (a *Agent) proxyTransport(ch ssh.Channel, reqC <-chan *ssh.Request) {
defer ch.Close()
var req *ssh.Request
select {
case req = <-reqC:
if req == nil {
log.Infof("connection closed, returning")
return
}
case <-time.After(10 * time.Second):
log.Errorf("timeout waiting for dial")
return
}
server := string(req.Payload)
log.Infof("got out of band request %v", server)
conn, err := net.Dial("tcp", server)
if err != nil {
log.Errorf("failed to dial: %v, err: %v", server, err)
return
}
req.Reply(true, []byte("connected"))
log.Infof("successfully dialed to %v, start proxying", server)
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
io.Copy(conn, ch)
}()
go func() {
defer wg.Done()
io.Copy(ch, conn)
}()
wg.Wait()
}
func (a *Agent) startHeartbeat() {
defer func() {
a.disconnectC <- true
log.Infof("sent disconnect message")
}()
hb, reqC, err := a.conn.OpenChannel(chanHeartbeat, nil)
if err != nil {
log.Errorf("failed to open channel: %v", err)
return
}
closeC := make(chan bool)
errC := make(chan error, 2)
go func() {
for {
select {
case <-closeC:
log.Infof("asked to exit")
return
default:
}
_, err := hb.SendRequest("ping", false, nil)
if err != nil {
log.Errorf("failed to send heartbeat: %v", err)
errC <- err
return
}
time.Sleep(heartbeatPeriod)
}
}()
go func() {
for {
select {
case <-closeC:
log.Infof("asked to exit")
return
case req := <-reqC:
if req == nil {
errC <- fmt.Errorf("heartbeat: connection closed")
return
}
log.Infof("got out of band request: %v", req)
}
}
}()
log.Infof("got error: %v", <-errC)
close(closeC)
}
const (
chanHeartbeat = "teleport-heartbeat"
chanAccessPoint = "teleport-access-point"
chanTransport = "teleport-transport"
chanTransportDialReq = "teleport-transport-dial"
heartbeatPeriod = 5 * time.Second
)
const (
RemoteSiteStatusOffline = "offline"
RemoteSiteStatusOnline = "online"
)

328
tun/srv.go Normal file
View file

@ -0,0 +1,328 @@
package tun
import (
"fmt"
"net"
"net/http"
"sync"
"time"
"github.com/gravitational/teleport/auth"
"github.com/gravitational/teleport/backend"
"github.com/gravitational/teleport/sshutils"
"github.com/gravitational/teleport/utils"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/gravitational/roundtrip"
"github.com/gravitational/teleport/Godeps/_workspace/src/github.com/mailgun/log"
"github.com/gravitational/teleport/Godeps/_workspace/src/golang.org/x/crypto/ssh"
"github.com/mailgun/oxy/forward"
)
type RemoteSite interface {
ConnectToServer(addr, user string, auth []ssh.AuthMethod) (*ssh.Client, error)
GetLastConnected() time.Time
GetName() string
GetServers() ([]backend.Server, error)
GetStatus() string
GetEvents() ([]interface{}, error)
}
type Server interface {
GetSites() []RemoteSite
GetSite(name string) (RemoteSite, error)
Start() error
Wait()
}
type server struct {
sync.RWMutex
certChecker ssh.CertChecker
l net.Listener
srv *sshutils.Server
sites []*remoteSite
}
// New returns an unstarted server
func NewServer(addr utils.NetAddr, hostSigners []ssh.Signer) (Server, error) {
srv := &server{
sites: []*remoteSite{},
}
s, err := sshutils.NewServer(
addr,
srv,
hostSigners,
sshutils.AuthMethods{
PublicKey: srv.keyAuth,
})
if err != nil {
return nil, err
}
srv.certChecker = ssh.CertChecker{IsAuthority: srv.isAuthority}
srv.srv = s
return srv, nil
}
func (s *server) Wait() {
s.srv.Wait()
}
func (s *server) Addr() string {
return s.srv.Addr()
}
func (s *server) Start() error {
return s.srv.Start()
}
func (s *server) Close() error {
return s.srv.Close()
}
func (s *server) HandleNewChan(sconn *ssh.ServerConn, nch ssh.NewChannel) {
log.Infof("got new channel request: %v", nch.ChannelType())
switch nch.ChannelType() {
case chanHeartbeat:
log.Infof("got heartbeat request from agent: %v", sconn)
site, err := s.upsertSite(sconn)
if err != nil {
log.Errorf("failed to upsert site: %v", err)
nch.Reject(ssh.ConnectionFailed, "failed to upsert site")
return
}
ch, req, err := nch.Accept()
if err != nil {
log.Errorf("failed to accept channel: %v", err)
sconn.Close()
return
}
go site.handleHeartbeat(ch, req)
}
}
// isAuthority is called during checking the client key, to see if the signing
// key is the real CA authority key.
func (s *server) isAuthority(auth ssh.PublicKey) bool {
return true
}
func (s *server) keyAuth(
conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
cid := fmt.Sprintf(
"conn(%v->%v, user=%v)", conn.RemoteAddr(),
conn.LocalAddr(), conn.User())
log.Infof("%v auth attempt with key %v", cid, key.Type())
return nil, nil
}
func (s *server) upsertSite(c ssh.Conn) (*remoteSite, error) {
s.Lock()
defer s.Unlock()
fqdn := c.User()
var site *remoteSite
for _, st := range s.sites {
if st.fqdn == fqdn {
site = st
break
}
}
if site != nil {
if err := site.init(c); err != nil {
return nil, err
}
} else {
site = &remoteSite{srv: s, fqdn: c.User()}
if err := site.init(c); err != nil {
return nil, err
}
s.sites = append(s.sites, site)
}
return site, nil
}
func (s *server) GetSites() []RemoteSite {
s.RLock()
defer s.RUnlock()
out := make([]RemoteSite, len(s.sites))
for i := range s.sites {
out[i] = s.sites[i]
}
return out
}
func (s *server) GetSite(fqdn string) (RemoteSite, error) {
s.RLock()
defer s.RUnlock()
for i := range s.sites {
if s.sites[i].fqdn == fqdn {
return s.sites[i], nil
}
}
return nil, fmt.Errorf("site not found")
}
type remoteSite struct {
fqdn string `json:"fqdn"`
conn ssh.Conn
lastActive time.Time
srv *server
clt *auth.Client
}
func (s *remoteSite) GetEvents() ([]interface{}, error) {
return s.clt.GetEvents()
}
func (s *remoteSite) String() string {
return fmt.Sprintf("remoteSite(%v)", s.fqdn)
}
func (s *remoteSite) init(c ssh.Conn) error {
if s.conn != nil {
log.Infof("%v found site, closing previous connection", s)
s.conn.Close()
}
s.conn = c
tr := &http.Transport{
Dial: func(network, addr string) (net.Conn, error) {
ch, _, err := s.conn.OpenChannel(chanAccessPoint, nil)
if err != nil {
log.Errorf("remoteSite:authProxy %v", err)
return nil, err
}
return newChConn(s.conn, ch), nil
},
}
clt, err := auth.NewClient(
"http://stub:0",
roundtrip.HTTPClient(&http.Client{
Transport: tr,
}))
if err != nil {
return err
}
s.clt = clt
return nil
}
func (s *remoteSite) GetStatus() string {
diff := time.Now().Sub(s.lastActive)
if diff > 2*heartbeatPeriod {
return RemoteSiteStatusOffline
}
return RemoteSiteStatusOnline
}
func (s *remoteSite) handleHeartbeat(ch ssh.Channel, reqC <-chan *ssh.Request) {
go func() {
for {
req := <-reqC
if req == nil {
log.Infof("agent disconnected")
return
}
log.Infof("%v -> ping", s)
s.lastActive = time.Now()
}
}()
}
func (s *remoteSite) GetName() string {
return s.fqdn
}
func (s *remoteSite) GetLastConnected() time.Time {
return s.lastActive
}
func (s *remoteSite) ConnectToServer(server, user string, auth []ssh.AuthMethod) (*ssh.Client, error) {
ch, _, err := s.conn.OpenChannel(chanTransport, nil)
if err != nil {
log.Errorf("remoteSite:connectToServer %v", err)
return nil, err
}
// ask remote channel to dial
dialed, err := ch.SendRequest(chanTransportDialReq, true, []byte(server))
if err != nil {
log.Errorf("failed to process request: %v", err)
return nil, err
}
if !dialed {
log.Errorf("remote end failed to dial: %v", err)
return nil, fmt.Errorf("remote server %v is not available", server)
}
transportConn := newChConn(s.conn, ch)
conn, chans, reqs, err := ssh.NewClientConn(
transportConn, server,
&ssh.ClientConfig{
User: user,
Auth: auth,
})
if err != nil {
log.Errorf("remoteSite:connectToServer %v", err)
return nil, err
}
return ssh.NewClient(conn, chans, reqs), nil
}
func (s *remoteSite) GetServers() ([]backend.Server, error) {
return s.clt.GetServers()
}
func (s *remoteSite) handleAuthProxy(w http.ResponseWriter, r *http.Request) {
tr := &http.Transport{
Dial: func(network, addr string) (net.Conn, error) {
ch, _, err := s.conn.OpenChannel(chanAccessPoint, nil)
if err != nil {
log.Errorf("remoteSite:authProxy %v", err)
return nil, err
}
return newChConn(s.conn, ch), nil
},
}
fwd, err := forward.New(forward.RoundTripper(tr), forward.Logger(log.GetLogger()))
if err != nil {
log.Errorf("write: %v", err)
roundtrip.ReplyJSON(w, http.StatusInternalServerError, err.Error())
return
}
r.URL.Scheme = "http"
r.URL.Host = "stub"
fwd.ServeHTTP(w, r)
}
func newChConn(conn ssh.Conn, ch ssh.Channel) *chConn {
c := &chConn{}
c.Channel = ch
c.conn = conn
return c
}
type chConn struct {
ssh.Channel
conn ssh.Conn
}
func (c *chConn) LocalAddr() net.Addr {
return c.conn.LocalAddr()
}
func (c *chConn) RemoteAddr() net.Addr {
return c.conn.RemoteAddr()
}
func (c *chConn) SetDeadline(t time.Time) error {
return nil
}
func (c *chConn) SetReadDeadline(t time.Time) error {
return nil
}
func (c *chConn) SetWriteDeadline(t time.Time) error {
return nil
}

View file

@ -9,6 +9,7 @@ import (
type NetAddr struct {
Addr string
Network string
Path string
}
func (a *NetAddr) String() string {
@ -22,7 +23,7 @@ func ParseAddr(a string) (*NetAddr, error) {
}
switch u.Scheme {
case "tcp":
return &NetAddr{Addr: u.Host, Network: u.Scheme}, nil
return &NetAddr{Addr: u.Host, Network: u.Scheme, Path: u.Path}, nil
case "unix":
return &NetAddr{Addr: u.Path, Network: u.Scheme}, nil
default: