mirror of
https://github.com/gravitational/teleport
synced 2024-10-19 16:53:57 +00:00
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:
parent
5ab374398c
commit
f59223256c
37
Makefile
37
Makefile
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
12
auth/auth.go
12
auth/auth.go
|
@ -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)
|
||||
|
|
42
auth/clt.go
42
auth/clt.go
|
@ -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.
|
||||
//
|
||||
|
|
|
@ -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) {
|
||||
|
|
58
auth/srv.go
58
auth/srv.go
|
@ -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"`
|
||||
}
|
||||
|
|
|
@ -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{})
|
||||
}
|
||||
|
|
11
auth/tun.go
11
auth/tun.go
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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{}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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{}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
1
cp/cp.go
1
cp/cp.go
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
75
srv/srv.go
75
srv/srv.go
|
@ -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)
|
||||
}
|
||||
}()
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
322
tun/agent.go
Normal 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
328
tun/srv.go
Normal 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
|
||||
}
|
|
@ -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:
|
||||
|
|
Loading…
Reference in a new issue