Merge remote-tracking branch 'origin/master' into ev/194

Conflicts:
	.gitignore
	web/dist/app/app
This commit is contained in:
Ev Kontsevoy 2016-03-04 16:28:49 -08:00
commit 9de557a98a
65 changed files with 2463 additions and 2022 deletions

1
.gitignore vendored
View file

@ -1,5 +1,6 @@
# Ev: this is temporary
assets/docker
web/dist/app/app
# Compiled binaries, Object files, Static and Dynamic libs (Shared Objects)
out

2
Godeps/Godeps.json generated
View file

@ -88,7 +88,7 @@
},
{
"ImportPath": "github.com/gravitational/trace",
"Rev": "af77d5facfcfa8cdccdae73865728c8c0ac3dbf6"
"Rev": "7d4c31f3b31deca858e7550117ff11f4b8abc928"
},
{
"ImportPath": "github.com/jonboulle/clockwork",

View file

@ -175,12 +175,12 @@ func (socket *fakeSocket) CreateBridge(remoteAddr net.Addr, sshChan ssh.Channel)
// Accept() will unblock this select
case socket.connections <- connection:
}
log.Debugf("SocketOverSSH.Handle(from=%v) is accepted", remoteAddr)
// wait for the connection to close:
select {
case <-connection.closed:
}
log.Debugf("SocketOverSSH.Handle(from=%v) is done", remoteAddr)
log.Debugf("SocketOverSSH.Handle(from=%v) is closed", remoteAddr)
return nil
}

View file

@ -345,7 +345,7 @@ func (s *APISuite) TestSharedSessionsParties(c *C) {
ID: "p1",
User: "bob",
RemoteAddr: "example.com",
ServerAddr: "localhost:1",
ServerID: "id-1",
LastActive: time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC),
}
c.Assert(s.clt.UpsertParty("s1", p1, 0), IsNil)

View file

@ -19,6 +19,7 @@ package auth
import (
"encoding/json"
"fmt"
"io"
"net"
"net/http"
"os"
@ -364,7 +365,7 @@ func (s *AuthTunnel) passwordAuth(
switch ab.Type {
case AuthWebPassword:
if err := s.authServer.CheckPassword(conn.User(), ab.Pass, ab.HotpToken); err != nil {
log.Errorf("Password auth error: %v", err)
log.Warningf("password auth error: %v", err)
return nil, trace.Wrap(err)
}
perms := &ssh.Permissions{
@ -510,7 +511,7 @@ func NewTunClient(addr utils.NetAddr, user string, auth []ssh.AuthMethod) (*TunC
return tc, nil
}
func (c *TunClient) GetAgent() (agent.Agent, error) {
func (c *TunClient) GetAgent() (AgentCloser, error) {
return c.dialer.GetAgent()
}
@ -525,63 +526,60 @@ func (c *TunClient) GetDialer() AccessPointDialer {
}
}
type AgentCloser interface {
io.Closer
agent.Agent
}
type tunAgent struct {
agent.Agent
client *ssh.Client
}
func (ta *tunAgent) Close() error {
log.Infof("tunAgent.Close")
return ta.client.Close()
}
type TunDialer struct {
sync.Mutex
auth []ssh.AuthMethod
user string
tun *ssh.Client
addr utils.NetAddr
}
func (t *TunDialer) Close() error {
if t.tun != nil {
return t.tun.Close()
}
return nil
}
func (t *TunDialer) GetAgent() (agent.Agent, error) {
_, err := t.getClient(false) // we need an established connection first
func (t *TunDialer) GetAgent() (AgentCloser, error) {
client, err := t.getClient() // we need an established connection first
if err != nil {
return nil, trace.Wrap(err)
return nil, trace.Wrap(
teleport.ConnectionProblem("failed to connect to remote API", err))
}
ch, _, err := t.tun.OpenChannel(ReqWebSessionAgent, nil)
ch, _, err := client.OpenChannel(ReqWebSessionAgent, nil)
if err != nil {
// reconnecting and trying again
_, err := t.getClient(true)
if err != nil {
return nil, trace.Wrap(err)
return nil, trace.Wrap(
teleport.ConnectionProblem("failed to connect to remote API", err))
}
ch, _, err = t.tun.OpenChannel(ReqWebSessionAgent, nil)
if err != nil {
return nil, trace.Wrap(err)
}
}
log.Infof("opened agent channel")
return agent.NewClient(ch), nil
agentCloser := &tunAgent{client: client}
agentCloser.Agent = agent.NewClient(ch)
return agentCloser, nil
}
func (t *TunDialer) getClient(reset bool) (*ssh.Client, error) {
t.Lock()
defer t.Unlock()
if t.tun != nil {
if !reset {
return t.tun, nil
}
go t.tun.Close()
t.tun = nil
}
func (t *TunDialer) getClient() (*ssh.Client, error) {
config := &ssh.ClientConfig{
User: t.user,
Auth: t.auth,
}
client, err := ssh.Dial(t.addr.AddrNetwork, t.addr.Addr, config)
log.Infof("TunDialer.getClient(%v)", t.addr.String())
if err != nil {
log.Infof("TunDialer could not ssh.Dial: %v", err)
return nil, trace.Wrap(err)
}
t.tun = client
return t.tun, nil
return client, nil
}
const (
@ -592,33 +590,34 @@ const (
DialerPeriodBetweenAttempts = time.Second
)
type tunConn struct {
net.Conn
client *ssh.Client
}
func (c *tunConn) Close() error {
log.Infof("tunConn: close!")
err := c.Conn.Close()
err = c.client.Close()
return trace.Wrap(err)
}
func (t *TunDialer) Dial(network, address string) (net.Conn, error) {
var client *ssh.Client
var err error
for i := 0; i < DialerRetryAttempts; i++ {
if i == 0 {
client, err = t.getClient(false)
log.Infof("TunDialer.Dial(%v, %v)", network, address)
client, err := t.getClient()
if err != nil {
log.Infof("TunDialer failed to get client: %v", err)
continue
}
} else {
time.Sleep(DialerPeriodBetweenAttempts)
client, err = t.getClient(true)
if err != nil {
log.Infof("TunDialer failed to get client: %v", err)
continue
}
}
conn, err := client.Dial(network, address)
if err == nil {
return conn, nil
}
log.Infof("TunDialer connection issue (%v), reconnect", err)
}
return nil, trace.Wrap(
teleport.ConnectionProblem("failed to connect to remote API", err))
}
conn, err := client.Dial(network, address)
if err != nil {
return nil, trace.Wrap(
teleport.ConnectionProblem("failed to connect to remote API", err))
}
tc := &tunConn{client: client}
tc.Conn = conn
return tc, nil
}
func NewClientFromSSHClient(sshClient *ssh.Client) (*Client, error) {
tr := &http.Transport{

View file

@ -36,18 +36,18 @@ type EncryptedBackend struct {
func newEncryptedBackend(backend backend.Backend, key encryptor.Key,
signKey encryptor.Key, signCheckingKeys []encryptor.Key) (*EncryptedBackend, error) {
var err error
ebk := EncryptedBackend{}
ebk.bk = backend
ebk.encryptor, err = encryptor.NewGPGEncryptor(key)
encryptor, err := encryptor.NewGPGEncryptor(key)
if err != nil {
log.Errorf(err.Error())
return nil, err
return nil, trace.Wrap(err)
}
ebk.prefix = []string{rootDir, key.ID}
ebk.KeyID = key.ID
ebk := &EncryptedBackend{
bk: backend,
encryptor: encryptor,
prefix: []string{rootDir, key.ID},
KeyID: key.ID,
}
if err := ebk.encryptor.SetSignKey(signKey); err != nil {
return nil, trace.Wrap(err)
@ -59,7 +59,7 @@ func newEncryptedBackend(backend backend.Backend, key encryptor.Key,
}
}
return &ebk, nil
return ebk, nil
}
// Add special value.
@ -72,10 +72,10 @@ func (b *EncryptedBackend) Sign() error {
func (b *EncryptedBackend) VerifySign() error {
val, err := b.GetVal([]string{}, "sign")
if err != nil {
return err
return trace.Wrap(err)
}
if string(val) != b.KeyID {
return trace.Errorf("can't verify sign")
return trace.Wrap(teleport.BadParameter("val", "can't verify signature"))
}
return nil
}

View file

@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package encryptor
type Encryptor interface {

View file

@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package encryptor
import (
@ -23,11 +24,11 @@ import (
"io/ioutil"
"sync"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
"github.com/gravitational/teleport"
"github.com/gravitational/trace"
"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/packet"
_ "golang.org/x/crypto/ripemd160"
)
@ -44,7 +45,8 @@ func NewGPGEncryptor(key Key) (*GPGEncryptor, error) {
e.Mutex = &sync.Mutex{}
if key.PublicValue == nil && key.PrivateValue == nil {
return nil, trace.Errorf("no values were found in the provided key")
return nil, trace.Wrap(
teleport.BadParameter("key", "no values were found in the provided key"))
}
if key.PublicValue != nil {
@ -73,7 +75,7 @@ func (e *GPGEncryptor) SetSignKey(key Key) error {
defer e.Unlock()
if key.PrivateValue == nil {
return trace.Errorf("no private key was provided in the sign key")
return trace.Wrap(teleport.BadParameter("key", "no private key was provided in the sign key"))
}
var err error
e.signEntity, err = openpgp.ReadEntity(
@ -89,7 +91,7 @@ func (e *GPGEncryptor) AddSignCheckingKey(key Key) error {
defer e.Unlock()
if key.PublicValue == nil {
return trace.Errorf("no public key was provided in the sign checking key")
return trace.Wrap(teleport.BadParameter("key", "no public key was provided in the sign key"))
}
signCheckingEntity, err := openpgp.ReadEntity(
packet.NewReader(bytes.NewBuffer(key.PublicValue)))
@ -139,7 +141,7 @@ func (e *GPGEncryptor) Encrypt(data []byte) ([]byte, error) {
}
entityList := openpgp.EntityList{e.publicEntity}
// encrypt string
buf := new(bytes.Buffer)
buf := &bytes.Buffer{}
w, err := openpgp.Encrypt(buf, entityList, e.signEntity, nil, nil)
if err != nil {
return nil, trace.Wrap(err)
@ -152,12 +154,10 @@ func (e *GPGEncryptor) Encrypt(data []byte) ([]byte, error) {
if err != nil {
return nil, trace.Wrap(err)
}
bytes, err := ioutil.ReadAll(buf)
if err != nil {
return nil, trace.Wrap(err)
}
hexString := hex.EncodeToString(bytes)
return []byte(hexString), nil

View file

@ -13,13 +13,15 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package encryptedbk
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"reflect"
"fmt"
"sync"
"time"
@ -31,96 +33,96 @@ import (
"github.com/gravitational/teleport/lib/backend/encryptedbk/encryptor"
)
// ReplicatedBackend is a backend that reads and writes copy of the same
// data to a set of replicas where each of replica is encrypted by
// it's own PGP key. So ReplicatedBackend encrypts the replicas
// using their pulbic keys, so each replica can decrypt the data using
// it's private PGP key
type ReplicatedBackend struct {
sync.Mutex
baseBk backend.Backend
ebk []*EncryptedBackend
mutex *sync.Mutex
encryptedBackends []*EncryptedBackend
keyStore KeyStore
signKey encryptor.Key
signCheckingKeys []encryptor.Key
keyGenerator encryptor.KeyGenerator
}
// NewReplicatedBackend returns a new instance of the replicated backend
func NewReplicatedBackend(backend backend.Backend, keysFile string,
additionalKeys []encryptor.Key,
keyGenerator encryptor.KeyGenerator) (*ReplicatedBackend, error) {
var err error
keyStore, err := NewKeyStore(keysFile)
if err != nil {
return nil, trace.Wrap(err)
}
backend.AcquireLock(bkLock, time.Minute)
defer backend.ReleaseLock(bkLock)
repBk := ReplicatedBackend{}
repBk.mutex = &sync.Mutex{}
repBk.mutex.Lock()
defer repBk.mutex.Unlock()
repBk.baseBk = backend
repBk.keyGenerator = keyGenerator
repBk.keyStore, err = NewKeyStore(keysFile)
if err != nil {
log.Errorf(err.Error())
return nil, err
}
repBackend := &ReplicatedBackend{
baseBk: backend,
keyGenerator: keyGenerator,
keyStore: keyStore,
}
for _, key := range additionalKeys {
if !repBk.keyStore.HasKey(key.ID) {
err := repBk.keyStore.AddKey(key)
if !repBackend.keyStore.HasKey(key.ID) {
err := repBackend.keyStore.AddKey(key)
if err != nil {
repBk.keyStore.Close()
repBackend.keyStore.Close()
return nil, trace.Wrap(err)
}
}
}
localKeys, err := repBk.keyStore.GetKeys()
localKeys, err := repBackend.keyStore.GetKeys()
if err != nil {
repBk.keyStore.Close()
repBackend.keyStore.Close()
return nil, trace.Wrap(err)
}
log.Infof("Available %v local seal keys:", len(localKeys))
log.Infof("available %v local seal keys", len(localKeys))
for _, key := range localKeys {
log.Infof(key.Name)
if err := repBk.addSignCheckingKey(key.Public()); err != nil {
repBk.keyStore.Close()
return nil, err
if err := repBackend.addSignCheckingKey(key.Public()); err != nil {
repBackend.keyStore.Close()
return nil, trace.Wrap(err)
}
if len(key.PrivateValue) != 0 {
if err := repBk.setSignKey(key, false); err != nil {
repBk.keyStore.Close()
return nil, err
if err := repBackend.setSignKey(key, false); err != nil {
repBackend.keyStore.Close()
return nil, trace.Wrap(err)
}
}
}
remoteKeys, err := backend.GetKeys([]string{rootDir})
if err != nil {
repBk.keyStore.Close()
repBackend.keyStore.Close()
return nil, trace.Wrap(err)
}
if len(remoteKeys) != 0 {
err = repBk.initFromExistingBk(additionalKeys)
err = repBackend.initFromExistingState(additionalKeys)
} else {
err = repBk.initFromEmptyBk()
err = repBackend.initFromEmptyState()
}
if err != nil {
log.Errorf(err.Error())
repBk.keyStore.Close()
return nil, err
repBackend.keyStore.Close()
return nil, trace.Wrap(err)
}
go repBackend.refreshKeys()
return repBackend, nil
}
go repBk.refreshKeys()
log.Infof("Backend was initialized")
return &repBk, nil
}
func (b *ReplicatedBackend) initFromExistingBk(additionalKeys []encryptor.Key) error {
log.Infof("Starting with an existing backend. Comparing local and remote keys.")
func (b *ReplicatedBackend) initFromExistingState(additionalKeys []encryptor.Key) error {
log.Infof("Starting with an existing state. Comparing local and remote keys.")
localKeys, err := b.getLocalSealKeys()
if err != nil {
log.Errorf(err.Error())
return trace.Wrap(err)
}
if len(localKeys) == 0 {
return trace.Errorf("can't initialize backend: no backend seal keys were provided")
return trace.Wrap(
teleport.BadParameter(
"localKeys", "can't initialize backend: no backend seal keys were provided"))
}
// first initialize only backends, that can decrypt data
@ -131,12 +133,14 @@ func (b *ReplicatedBackend) initFromExistingBk(additionalKeys []encryptor.Key) e
}
if bk.VerifySign() == nil {
b.ebk = append(b.ebk, bk)
b.encryptedBackends = append(b.encryptedBackends, bk)
}
}
if len(b.ebk) == 0 {
return trace.Errorf("can't initialize backend: no valid backend seal keys were provided")
if len(b.encryptedBackends) == 0 {
return trace.Wrap(
teleport.BadParameter(
"localKeys", "can't initialize backend: no backend seal keys were provided"))
}
if err := b.updateLocalKeysFromCluster(); err != nil {
@ -145,21 +149,21 @@ func (b *ReplicatedBackend) initFromExistingBk(additionalKeys []encryptor.Key) e
return nil
}
func (b *ReplicatedBackend) initFromEmptyBk() error {
const defaultKey = "default key"
func (b *ReplicatedBackend) initFromEmptyState() error {
log.Infof("Starting with empty backend")
localKeys, err := b.getLocalSealKeys()
if err != nil {
log.Errorf(err.Error())
return err
return trace.Wrap(err)
}
if len(localKeys) == 0 {
log.Infof("No local backend encrypting keys were found, generating new key 'default key'")
_, err := b.generateSealKey("default key", false)
return err
} else {
log.Infof("No local backend encrypting keys were found, generating new key '%v'", defaultKey)
_, err := b.generateSealKey(defaultKey, false)
return trace.Wrap(err)
}
for _, key := range localKeys {
bk, err := newEncryptedBackend(b.baseBk, key,
b.signKey, b.signCheckingKeys)
@ -170,7 +174,7 @@ func (b *ReplicatedBackend) initFromEmptyBk() error {
if err != nil {
return trace.Wrap(err)
}
b.ebk = append(b.ebk, bk)
b.encryptedBackends = append(b.encryptedBackends, bk)
if len(key.PrivateValue) != 0 {
if err := bk.VerifySign(); err != nil {
return trace.Wrap(err)
@ -187,28 +191,28 @@ func (b *ReplicatedBackend) initFromEmptyBk() error {
}
}
return nil
}
}
func (b *ReplicatedBackend) refreshKeys() {
for {
time.Sleep(time.Minute)
if err := b.updateLocalKeysFromCluster(); err != nil {
if err := b.UpdateLocalKeysFromCluster(); err != nil {
log.Errorf(err.Error())
}
}
}
func (b *ReplicatedBackend) GetKeys(path []string) ([]string, error) {
b.mutex.Lock()
defer b.mutex.Unlock()
b.Lock()
defer b.Unlock()
return b.getKeys(path)
}
func (b *ReplicatedBackend) getKeys(path []string) ([]string, error) {
var e error
e = trace.Errorf("")
for _, bk := range b.ebk {
for _, bk := range b.encryptedBackends {
if bk.VerifySign() == nil {
var keys []string
keys, err := bk.GetKeys(path)
@ -222,8 +226,8 @@ func (b *ReplicatedBackend) getKeys(path []string) ([]string, error) {
}
func (b *ReplicatedBackend) DeleteKey(path []string, key string) error {
b.mutex.Lock()
defer b.mutex.Unlock()
b.Lock()
defer b.Unlock()
return b.deleteKey(path, key)
}
@ -231,7 +235,7 @@ func (b *ReplicatedBackend) deleteKey(path []string, key string) error {
var resultErr error
resultErr = nil
for _, bk := range b.ebk {
for _, bk := range b.encryptedBackends {
err := bk.DeleteKey(path, key)
if err != nil {
resultErr = err
@ -241,13 +245,13 @@ func (b *ReplicatedBackend) deleteKey(path []string, key string) error {
}
func (b *ReplicatedBackend) DeleteBucket(path []string, bkt string) error {
b.mutex.Lock()
defer b.mutex.Unlock()
b.Lock()
defer b.Unlock()
var resultErr error
resultErr = nil
for _, bk := range b.ebk {
for _, bk := range b.encryptedBackends {
err := bk.DeleteBucket(path, bkt)
if err != nil {
resultErr = err
@ -257,23 +261,23 @@ func (b *ReplicatedBackend) DeleteBucket(path []string, bkt string) error {
}
func (b *ReplicatedBackend) UpsertVal(path []string, key string, val []byte, ttl time.Duration) error {
b.mutex.Lock()
defer b.mutex.Unlock()
b.Lock()
defer b.Unlock()
return b.upsertVal(path, key, val, ttl)
}
func (b *ReplicatedBackend) CreateVal(path []string, key string, val []byte, ttl time.Duration) error {
b.mutex.Lock()
defer b.mutex.Unlock()
b.Lock()
defer b.Unlock()
return b.createVal(path, key, val, ttl)
}
func (b *ReplicatedBackend) TouchVal(path []string, key string, ttl time.Duration) error {
b.mutex.Lock()
defer b.mutex.Unlock()
b.Lock()
defer b.Unlock()
var err error
for _, bk := range b.ebk {
for _, bk := range b.encryptedBackends {
err = bk.TouchVal(path, key, ttl)
}
@ -282,7 +286,7 @@ func (b *ReplicatedBackend) TouchVal(path []string, key string, ttl time.Duratio
func (b *ReplicatedBackend) upsertVal(path []string, key string, val []byte, ttl time.Duration) error {
var err error
for _, bk := range b.ebk {
for _, bk := range b.encryptedBackends {
err = bk.UpsertVal(path, key, val, ttl)
}
return trace.Wrap(err)
@ -290,15 +294,15 @@ func (b *ReplicatedBackend) upsertVal(path []string, key string, val []byte, ttl
func (b *ReplicatedBackend) createVal(path []string, key string, val []byte, ttl time.Duration) error {
var err error
for _, bk := range b.ebk {
for _, bk := range b.encryptedBackends {
err = bk.CreateVal(path, key, val, ttl)
}
return trace.Wrap(err)
}
func (b *ReplicatedBackend) CompareAndSwap(path []string, key string, val []byte, ttl time.Duration, prevVal []byte) ([]byte, error) {
b.mutex.Lock()
defer b.mutex.Unlock()
b.Lock()
defer b.Unlock()
b.baseBk.AcquireLock(bkLock, time.Minute)
defer b.baseBk.ReleaseLock(bkLock)
@ -314,23 +318,23 @@ func (b *ReplicatedBackend) CompareAndSwap(path []string, key string, val []byte
bothAreEmpty := len(storedVal) == 0 && len(prevVal) == 0
if bothAreEmpty || reflect.DeepEqual(storedVal, prevVal) {
if bothAreEmpty || bytes.Equal(storedVal, prevVal) {
return storedVal, b.upsertVal(path, key, val, ttl)
}
return storedVal, &teleport.CompareFailedError{}
return storedVal, trace.Wrap(&teleport.CompareFailedError{})
}
func (b *ReplicatedBackend) GetVal(path []string, key string) ([]byte, error) {
b.mutex.Lock()
defer b.mutex.Unlock()
b.Lock()
defer b.Unlock()
return b.getVal(path, key)
}
func (b *ReplicatedBackend) getVal(path []string, key string) ([]byte, error) {
err := trace.Errorf("can't decrypt data or check signature: no valid keys")
for _, bk := range b.ebk {
for _, bk := range b.encryptedBackends {
if bk.VerifySign() == nil {
var val []byte
val, err = bk.GetVal(path, key)
@ -343,14 +347,14 @@ func (b *ReplicatedBackend) getVal(path []string, key string) ([]byte, error) {
}
func (b *ReplicatedBackend) GetValAndTTL(path []string, key string) ([]byte, time.Duration, error) {
b.mutex.Lock()
defer b.mutex.Unlock()
b.Lock()
defer b.Unlock()
return b.getValAndTTL(path, key)
}
func (b *ReplicatedBackend) getValAndTTL(path []string, key string) ([]byte, time.Duration, error) {
err := trace.Errorf("can't decrypt data or check signature: no valid keys")
for _, bk := range b.ebk {
for _, bk := range b.encryptedBackends {
if bk.VerifySign() == nil {
var val []byte
var ttl time.Duration
@ -364,18 +368,18 @@ func (b *ReplicatedBackend) getValAndTTL(path []string, key string) ([]byte, tim
}
func (b *ReplicatedBackend) AcquireLock(token string, ttl time.Duration) error {
log.Infof("Acquire")
log.Infof("AcquireLock(token=%v, ttl=%v)", token, ttl)
return b.baseBk.AcquireLock(token, ttl)
}
func (b *ReplicatedBackend) ReleaseLock(token string) error {
log.Infof("Release")
log.Infof("ReleaseLock(token=%v)", token)
return b.baseBk.ReleaseLock(token)
}
func (b *ReplicatedBackend) GenerateSealKey(name string) (encryptor.Key, error) {
b.mutex.Lock()
defer b.mutex.Unlock()
b.Lock()
defer b.Unlock()
b.baseBk.AcquireLock(bkLock, time.Minute)
defer b.baseBk.ReleaseLock(bkLock)
return b.generateSealKey(name, true)
@ -388,7 +392,8 @@ func (b *ReplicatedBackend) generateSealKey(name string, copyData bool) (encrypt
}
for _, key := range localKeys {
if key.Name == name {
return encryptor.Key{}, trace.Errorf("key with name '" + name + "' already exists")
return encryptor.Key{}, trace.Wrap(
teleport.AlreadyExists(fmt.Sprintf("key with name '%v' already exists", name)))
}
}
@ -411,8 +416,8 @@ func (b *ReplicatedBackend) generateSealKey(name string, copyData bool) (encrypt
}
func (b *ReplicatedBackend) GetSealKey(id string) (encryptor.Key, error) {
b.mutex.Lock()
defer b.mutex.Unlock()
b.Lock()
defer b.Unlock()
b.baseBk.AcquireLock(bkLock, time.Minute)
defer b.baseBk.ReleaseLock(bkLock)
return b.getLocalSealKey(id)
@ -423,8 +428,8 @@ func (b *ReplicatedBackend) getLocalSealKey(id string) (encryptor.Key, error) {
}
func (b *ReplicatedBackend) GetSealKeys() ([]encryptor.Key, error) {
b.mutex.Lock()
defer b.mutex.Unlock()
b.Lock()
defer b.Unlock()
b.baseBk.AcquireLock(bkLock, time.Minute)
defer b.baseBk.ReleaseLock(bkLock)
return b.getLocalSealKeys()
@ -435,32 +440,32 @@ func (b *ReplicatedBackend) getLocalSealKeys() ([]encryptor.Key, error) {
}
func (b *ReplicatedBackend) AddSealKey(key encryptor.Key) error {
b.mutex.Lock()
defer b.mutex.Unlock()
b.Lock()
defer b.Unlock()
b.baseBk.AcquireLock(bkLock, time.Minute)
defer b.baseBk.ReleaseLock(bkLock)
return b.addSealKey(key, true)
}
func (b *ReplicatedBackend) addSealKey(key encryptor.Key, copyData bool) error {
log.Infof("Adding backend seal key '" + key.Name + "'")
log.Infof("Adding backend seal key '%v'", key.Name)
if len(key.Name) == 0 {
return trace.Errorf("key name is not provided")
return trace.Wrap(teleport.BadParameter("key.Name", "key name is not provided"))
}
keySha1 := sha256.Sum256(key.PublicValue)
keyHash := hex.EncodeToString(keySha1[:])
if !reflect.DeepEqual(key.ID, keyHash) {
return trace.Errorf("key is corrupted, key id mismatch key value")
if key.ID != keyHash {
return trace.Wrap(teleport.BadParameter("key.ID", "key is corrupted, key id mismatch key value"))
}
_, err := b.getLocalSealKey(key.ID)
if err == nil {
return &teleport.AlreadyExistsError{Message: "Error: Key " + key.ID + " already exists"}
return trace.Wrap(
teleport.AlreadyExists(fmt.Sprintf("key %v already exists", key.ID)))
}
if !teleport.IsNotFound(err) {
log.Errorf(err.Error())
return trace.Wrap(err)
}
@ -487,11 +492,11 @@ func (b *ReplicatedBackend) addSealKey(key encryptor.Key, copyData bool) error {
return trace.Wrap(err)
}
if copyData && len(b.ebk) > 0 {
if copyData && len(b.encryptedBackends) > 0 {
copied := false
for _, ebk := range b.ebk {
for _, ebk := range b.encryptedBackends {
if ebk.VerifySign() == nil {
err = b.copy(b.ebk[0], bk, []string{})
err = b.copy(b.encryptedBackends[0], bk, []string{})
if err != nil {
log.Errorf(err.Error())
bk.DeleteAll()
@ -506,7 +511,7 @@ func (b *ReplicatedBackend) addSealKey(key encryptor.Key, copyData bool) error {
}
}
b.ebk = append(b.ebk, bk)
b.encryptedBackends = append(b.encryptedBackends, bk)
if err := b.addSignCheckingKey(key); err != nil {
return trace.Wrap(err)
@ -530,8 +535,8 @@ func (b *ReplicatedBackend) addSealKey(key encryptor.Key, copyData bool) error {
}
func (b *ReplicatedBackend) DeleteSealKey(id string) error {
b.mutex.Lock()
defer b.mutex.Unlock()
b.Lock()
defer b.Unlock()
b.baseBk.AcquireLock(bkLock, time.Minute)
defer b.baseBk.ReleaseLock(bkLock)
return b.deleteSealKey(id, true)
@ -540,7 +545,7 @@ func (b *ReplicatedBackend) DeleteSealKey(id string) error {
func (b *ReplicatedBackend) deleteSealKey(id string, rewriteData bool) error {
anotherValidKey := false
var anotherKey encryptor.Key
for _, bk := range b.ebk {
for _, bk := range b.encryptedBackends {
if bk.KeyID != id && bk.VerifySign() == nil {
var err error
anotherKey, err = b.keyStore.GetKey(bk.KeyID)
@ -576,9 +581,9 @@ func (b *ReplicatedBackend) deleteSealKey(id string, rewriteData bool) error {
log.Infof("Key %s was deleted from local keys", id)
}
for i, bk := range b.ebk {
for i, bk := range b.encryptedBackends {
if bk.KeyID == id {
b.ebk = append(b.ebk[:i], b.ebk[i+1:]...)
b.encryptedBackends = append(b.encryptedBackends[:i], b.encryptedBackends[i+1:]...)
break
}
}
@ -627,13 +632,13 @@ func (b *ReplicatedBackend) getClusterPublicSealKeys() ([]encryptor.Key, error)
}
func (b *ReplicatedBackend) SetSignKey(key encryptor.Key) error {
b.mutex.Lock()
defer b.mutex.Unlock()
b.Lock()
defer b.Unlock()
return b.setSignKey(key, true)
}
func (b *ReplicatedBackend) setSignKey(key encryptor.Key, rewriteData bool) error {
for _, ebk := range b.ebk {
for _, ebk := range b.encryptedBackends {
err := ebk.encryptor.SetSignKey(key)
if err != nil {
return trace.Wrap(err)
@ -647,8 +652,8 @@ func (b *ReplicatedBackend) setSignKey(key encryptor.Key, rewriteData bool) erro
}
func (b *ReplicatedBackend) GetSignKey() (encryptor.Key, error) {
b.mutex.Lock()
defer b.mutex.Unlock()
b.Lock()
defer b.Unlock()
if len(b.signKey.PrivateValue) == 0 {
return encryptor.Key{}, trace.Errorf("sign key is not set")
}
@ -657,7 +662,7 @@ func (b *ReplicatedBackend) GetSignKey() (encryptor.Key, error) {
}
func (b *ReplicatedBackend) addSignCheckingKey(key encryptor.Key) error {
for _, ebk := range b.ebk {
for _, ebk := range b.encryptedBackends {
err := ebk.encryptor.AddSignCheckingKey(key)
if err != nil {
return trace.Wrap(err)
@ -675,7 +680,7 @@ func (b *ReplicatedBackend) deleteSignCheckingKey(id string) error {
}
}
for _, bk := range b.ebk {
for _, bk := range b.encryptedBackends {
if err := bk.encryptor.DeleteSignCheckingKey(id); err != nil {
return trace.Wrap(err)
}
@ -685,14 +690,14 @@ func (b *ReplicatedBackend) deleteSignCheckingKey(id string) error {
}
func (b *ReplicatedBackend) RewriteData() error {
b.mutex.Lock()
defer b.mutex.Unlock()
b.Lock()
defer b.Unlock()
return b.rewriteData()
}
func (b *ReplicatedBackend) rewriteData() error {
var srcBk *EncryptedBackend = nil
for _, bk := range b.ebk {
for _, bk := range b.encryptedBackends {
if bk.VerifySign() == nil {
srcBk = bk
}
@ -702,7 +707,7 @@ func (b *ReplicatedBackend) rewriteData() error {
return trace.Errorf("no valid backend keys to decrypt data")
}
for _, bk := range b.ebk {
for _, bk := range b.encryptedBackends {
if err := b.copy(srcBk, bk, []string{}); err != nil {
return trace.Wrap(err)
}
@ -760,8 +765,8 @@ func (b *ReplicatedBackend) deleteClusterPublicKey(keyID string) error {
}
func (b *ReplicatedBackend) UpdateLocalKeysFromCluster() error {
b.mutex.Lock()
defer b.mutex.Unlock()
b.Lock()
defer b.Unlock()
return b.updateLocalKeysFromCluster()
}
@ -779,7 +784,7 @@ func (b *ReplicatedBackend) updateLocalKeysFromCluster() error {
// initialize backends from active public keys
for _, key := range activeKeys {
alreadyInitialized := false
for _, bk := range b.ebk {
for _, bk := range b.encryptedBackends {
if bk.KeyID == key.ID {
alreadyInitialized = true
break
@ -791,7 +796,7 @@ func (b *ReplicatedBackend) updateLocalKeysFromCluster() error {
if err != nil {
return trace.Wrap(err)
}
b.ebk = append(b.ebk, bk)
b.encryptedBackends = append(b.encryptedBackends, bk)
if err := b.addSignCheckingKey(key); err != nil {
return trace.Wrap(err)
}

View file

@ -90,7 +90,7 @@ func (s *ReplicatedBkSuite) TestDataIsEncrypted(c *C) {
c.Assert(string(out), Equals, "val1")
// checking value as it saved
out, err = s.bk.baseBk.GetVal(append(s.bk.ebk[0].prefix, "a", "b"), "bkey")
out, err = s.bk.baseBk.GetVal(append(s.bk.encryptedBackends[0].prefix, "a", "b"), "bkey")
c.Assert(err, IsNil)
c.Assert(string(out) == "val1", Equals, false)
@ -115,12 +115,12 @@ func (s *ReplicatedBkSuite) TestSeveralKeys(c *C) {
key2, err := s.bk.GenerateSealKey("key2")
///c.Assert(s.bk.ebk[1].encryptor.AddSignCheckingKey(keys[0]), IsNil) ///
val, err = s.bk.ebk[1].GetVal([]string{"a1"}, "b1")
val, err = s.bk.encryptedBackends[1].GetVal([]string{"a1"}, "b1")
c.Assert(err, IsNil)
c.Assert(string(val), Equals, "val1")
c.Assert(s.bk.ebk[0].VerifySign(), IsNil)
c.Assert(s.bk.ebk[1].VerifySign(), IsNil)
c.Assert(s.bk.encryptedBackends[0].VerifySign(), IsNil)
c.Assert(s.bk.encryptedBackends[1].VerifySign(), IsNil)
c.Assert(s.bk.RewriteData(), IsNil)
@ -278,7 +278,7 @@ func (s *ReplicatedBkSuite) TestSeveralAuthServers(c *C) {
encryptor.GenerateGPGKey)
c.Assert(err, IsNil)
c.Assert(len(bk4.ebk), Equals, 3)
c.Assert(len(bk4.encryptedBackends), Equals, 3)
bk4Keys, err := bk4.keyStore.GetKeys()
c.Assert(err, IsNil)
c.Assert(len(bk4Keys), Equals, 3)

View file

@ -19,6 +19,7 @@ limitations under the License.
package test
import (
"sync/atomic"
"testing"
"time"
@ -196,43 +197,43 @@ func (s *BackendSuite) Locking(c *C) {
c.Assert(s.B.ReleaseLock(tok1), FitsTypeOf, &teleport.NotFoundError{})
c.Assert(s.B.AcquireLock(tok1, 30*time.Second), IsNil)
x := 7
c.Assert(s.B.AcquireLock(tok1, time.Second), IsNil)
x := int32(7)
go func() {
time.Sleep(1 * time.Second)
x = 9
atomic.StoreInt32(&x, 9)
c.Assert(s.B.ReleaseLock(tok1), IsNil)
}()
c.Assert(s.B.AcquireLock(tok1, 0), IsNil)
x = x * 2
c.Assert(x, Equals, 18)
atomic.AddInt32(&x, 9)
c.Assert(atomic.LoadInt32(&x), Equals, int32(18))
c.Assert(s.B.ReleaseLock(tok1), IsNil)
c.Assert(s.B.AcquireLock(tok1, 0), IsNil)
x = 7
atomic.StoreInt32(&x, 7)
go func() {
time.Sleep(1 * time.Second)
x = 9
atomic.StoreInt32(&x, 9)
c.Assert(s.B.ReleaseLock(tok1), IsNil)
}()
c.Assert(s.B.AcquireLock(tok1, 0), IsNil)
x = x * 2
c.Assert(x, Equals, 18)
atomic.AddInt32(&x, 9)
c.Assert(atomic.LoadInt32(&x), Equals, int32(18))
c.Assert(s.B.ReleaseLock(tok1), IsNil)
y := 0
go func() {
y := int32(0)
c.Assert(s.B.AcquireLock(tok1, 0), IsNil)
c.Assert(s.B.AcquireLock(tok2, 0), IsNil)
go func() {
atomic.StoreInt32(&y, 15)
c.Assert(s.B.ReleaseLock(tok1), IsNil)
c.Assert(s.B.ReleaseLock(tok2), IsNil)
y = 15
}()
time.Sleep(1 * time.Second)
c.Assert(y, Equals, 15)
c.Assert(s.B.AcquireLock(tok1, 0), IsNil)
c.Assert(atomic.LoadInt32(&y), Equals, int32(15))
c.Assert(s.B.ReleaseLock(tok1), IsNil)
c.Assert(s.B.ReleaseLock(tok1), FitsTypeOf, &teleport.NotFoundError{})
}

View file

@ -71,7 +71,7 @@ type HangoutsSuite struct {
var _ = Suite(&HangoutsSuite{})
func (s *HangoutsSuite) SetUpSuite(c *C) {
utils.InitLoggerDebug()
utils.InitLoggerCLI()
client.KeysDir = c.MkDir()
s.dir = c.MkDir()

View file

@ -39,8 +39,8 @@ type Chunk struct {
Data []byte `json:"data"`
// Delay is delay before the previous chunk appeared
Delay time.Duration `json:"delay"`
// ServerAddr is a server address of the recorded session
ServerAddr string `json:"server_addr"`
// ServerID is a server ID of the recorded session
ServerID string `json:"server_id"`
}
// ChunkReader is a playback of a recorded session

View file

@ -35,9 +35,9 @@ func (s *RecorderSuite) Recorder(c *C) {
w, err := s.R.GetChunkWriter("recs1")
c.Assert(err, IsNil)
c1 := recorder.Chunk{Data: []byte("chunk1"), ServerAddr: "127.0.0.1:2000"}
c2 := recorder.Chunk{Delay: 3 * time.Millisecond, Data: []byte("chunk2"), ServerAddr: "127.0.0.1:2001"}
c3 := recorder.Chunk{Delay: 5 * time.Millisecond, Data: []byte("chunk3"), ServerAddr: "127.0.0.1:2002"}
c1 := recorder.Chunk{Data: []byte("chunk1"), ServerID: "id1"}
c2 := recorder.Chunk{Delay: 3 * time.Millisecond, Data: []byte("chunk2"), ServerID: "id2"}
c3 := recorder.Chunk{Delay: 5 * time.Millisecond, Data: []byte("chunk3"), ServerID: "id3"}
c.Assert(w.WriteChunks([]recorder.Chunk{c1, c2}), IsNil)

View file

@ -362,6 +362,10 @@ func (s *server) upsertRegularSite(conn net.Conn, sshConn *ssh.ServerConn) (*tun
return nil, trace.Wrap(teleport.BadParameter(
"authDomain", fmt.Sprintf("'%v' is a bad domain name", domainName)))
}
s.Lock()
defer s.Unlock()
var site *tunnelSite
for _, st := range s.tunnelSites {
if st.domainName == domainName {
@ -371,9 +375,6 @@ func (s *server) upsertRegularSite(conn net.Conn, sshConn *ssh.ServerConn) (*tun
}
log.Infof("found authority domain: %v", domainName)
s.Lock()
defer s.Unlock()
var err error
if site != nil {
if err := site.addConn(conn, sshConn); err != nil {
@ -392,16 +393,24 @@ func (s *server) upsertRegularSite(conn net.Conn, sshConn *ssh.ServerConn) (*tun
return site, nil
}
func (s *server) upsertHangoutSite(conn net.Conn, sshConn ssh.Conn) (*tunnelSite, error) {
func (s *server) tryInsertHangoutSite(hangoutID string, remoteSite *tunnelSite) error {
s.Lock()
defer s.Unlock()
hangoutID := sshConn.User()
for _, st := range s.tunnelSites {
if st.domainName == hangoutID {
return nil, trace.Errorf("Hangout ID is already used")
return trace.Wrap(
teleport.BadParameter("hangoutID",
fmt.Sprintf("%v hangout id is already used", hangoutID)))
}
}
s.tunnelSites = append(s.tunnelSites, remoteSite)
return nil
}
func (s *server) upsertHangoutSite(conn net.Conn, sshConn ssh.Conn) (*tunnelSite, error) {
hangoutID := sshConn.User()
site, err := newRemoteSite(s, hangoutID)
if err != nil {
@ -441,26 +450,36 @@ func (s *server) upsertHangoutSite(conn net.Conn, sshConn ssh.Conn) (*tunnelSite
}
// receiving hangoutInfo using sessions just as storage
sess, err := clt.GetSessions()
sessions, err := clt.GetSessions()
if err != nil {
return nil, trace.Wrap(err)
}
if len(sess) != 1 {
return nil, trace.Wrap(&teleport.NotFoundError{
Message: fmt.Sprintf("hangout %v not found", hangoutID),
})
}
hangoutInfo, err := utils.UnmarshalHangoutInfo(sess[0].ID)
var hangoutInfo *utils.HangoutInfo
for _, sess := range sessions {
info, err := utils.UnmarshalHangoutInfo(sess.ID)
if err != nil {
return nil, err
log.Infof("failed to unmarshal hangout info: %v", err)
}
if info.HangoutID == hangoutID {
hangoutInfo = info
break
}
}
if hangoutInfo == nil {
return nil, trace.Wrap(teleport.NotFound(
fmt.Sprintf("hangout %v not found", hangoutID)))
}
site.domainName = hangoutInfo.HangoutID
// TODO(klizhentas) refactor this
site.domainName = hangoutInfo.HangoutID
site.hangoutInfo.OSUser = hangoutInfo.OSUser
site.hangoutInfo.AuthPort = hangoutInfo.AuthPort
site.hangoutInfo.NodePort = hangoutInfo.NodePort
s.tunnelSites = append(s.tunnelSites, site)
if err := s.tryInsertHangoutSite(hangoutID, site); err != nil {
defer conn.Close()
return nil, trace.Wrap(err)
}
return site, nil
}
@ -677,6 +696,12 @@ func (s *tunnelSite) GetStatus() string {
return RemoteSiteStatusOnline
}
func (s *tunnelSite) setLastActive(t time.Time) {
s.Lock()
defer s.Unlock()
s.lastActive = t
}
func (s *tunnelSite) handleHeartbeat(ch ssh.Channel, reqC <-chan *ssh.Request) {
go func() {
for {
@ -685,7 +710,7 @@ func (s *tunnelSite) handleHeartbeat(ch ssh.Channel, reqC <-chan *ssh.Request) {
s.log.Infof("agent disconnected")
return
}
s.lastActive = time.Now()
s.setLastActive(time.Now())
}
}()
}
@ -695,6 +720,8 @@ func (s *tunnelSite) GetName() string {
}
func (s *tunnelSite) GetLastConnected() time.Time {
s.Lock()
defer s.Unlock()
return s.lastActive
}

View file

@ -13,9 +13,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package services
import (
"sync/atomic"
"time"
"github.com/gravitational/teleport"
@ -262,41 +264,40 @@ func (s *ServicesTestSuite) Locking(c *C) {
c.Assert(s.LockS.ReleaseLock(tok1), FitsTypeOf, &teleport.NotFoundError{})
c.Assert(s.LockS.AcquireLock(tok1, 30*time.Second), IsNil)
x := 7
x := int32(7)
go func() {
time.Sleep(1 * time.Second)
x = 9
atomic.StoreInt32(&x, 9)
c.Assert(s.LockS.ReleaseLock(tok1), IsNil)
}()
c.Assert(s.LockS.AcquireLock(tok1, 0), IsNil)
x = x * 2
c.Assert(x, Equals, 18)
atomic.AddInt32(&x, 9)
c.Assert(atomic.LoadInt32(&x), Equals, int32(18))
c.Assert(s.LockS.ReleaseLock(tok1), IsNil)
c.Assert(s.LockS.AcquireLock(tok1, 0), IsNil)
x = 7
atomic.StoreInt32(&x, 7)
go func() {
time.Sleep(1 * time.Second)
x = 9
atomic.StoreInt32(&x, 9)
c.Assert(s.LockS.ReleaseLock(tok1), IsNil)
}()
c.Assert(s.LockS.AcquireLock(tok1, 0), IsNil)
x = x * 2
c.Assert(x, Equals, 18)
atomic.AddInt32(&x, 9)
c.Assert(atomic.LoadInt32(&x), Equals, int32(18))
c.Assert(s.LockS.ReleaseLock(tok1), IsNil)
y := 0
y := int32(0)
go func() {
c.Assert(s.LockS.AcquireLock(tok1, 0), IsNil)
c.Assert(s.LockS.AcquireLock(tok2, 0), IsNil)
c.Assert(s.LockS.ReleaseLock(tok1), IsNil)
c.Assert(s.LockS.ReleaseLock(tok2), IsNil)
y = 15
atomic.StoreInt32(&y, 15)
}()
time.Sleep(1 * time.Second)
c.Assert(y, Equals, 15)
c.Assert(atomic.LoadInt32(&y), Equals, int32(15))
c.Assert(s.LockS.ReleaseLock(tok1), FitsTypeOf, &teleport.NotFoundError{})
}

View file

@ -355,7 +355,6 @@ func (s *WebService) CheckPassword(user string, password []byte, hotpToken strin
if err != nil {
return trace.Wrap(err)
}
if !otp.Scan(hotpToken, 4) {
return &teleport.BadParameterError{Err: "tokens do not match", Param: "token"}
}

View file

@ -60,8 +60,8 @@ type Party struct {
RemoteAddr string `json:"remote_addr"`
// User is a teleport user using this session
User string `json:"user"`
// ServerAddr is an address of the server
ServerAddr string `json:"server_addr"`
// ServerID is an address of the server
ServerID string `json:"server_id"`
// LastActive is a last time this party was active
LastActive time.Time `json:"last_active"`
}
@ -70,7 +70,7 @@ type Party struct {
func (p *Party) String() string {
return fmt.Sprintf(
"party(id=%v, remote=%v, user=%v, server=%v, last_active=%v)",
p.ID, p.RemoteAddr, p.User, p.ServerAddr, p.LastActive,
p.ID, p.RemoteAddr, p.User, p.ServerID, p.LastActive,
)
}

View file

@ -147,7 +147,7 @@ func (s *BoltSuite) TestPartiesCRUD(c *C) {
ID: "p1",
User: "bob",
RemoteAddr: "example.com",
ServerAddr: "localhost:1",
ServerID: "id-1",
LastActive: s.clock.UtcNow(),
}
c.Assert(s.srv.UpsertParty(sess.ID, p1, DefaultActivePartyTTL), IsNil)
@ -162,7 +162,7 @@ func (s *BoltSuite) TestPartiesCRUD(c *C) {
ID: "p2",
User: "alice",
RemoteAddr: "example.com",
ServerAddr: "localhost:2",
ServerID: "id-2",
LastActive: s.clock.UtcNow(),
}
c.Assert(s.srv.UpsertParty(sess.ID, p2, DefaultActivePartyTTL), IsNil)

View file

@ -56,11 +56,17 @@ func (s *sessionRegistry) newShell(sid string, sconn *ssh.ServerConn, ch ssh.Cha
if err := sess.start(sconn, ch, ctx); err != nil {
return trace.Wrap(err)
}
s.sessions[sess.id] = sess
s.addSession(sess)
ctx.Infof("created session: %v", sess.id)
return nil
}
func (s *sessionRegistry) addSession(sess *session) {
s.Lock()
defer s.Unlock()
s.sessions[sess.id] = sess
}
func (s *sessionRegistry) joinShell(sid string, sconn *ssh.ServerConn, ch ssh.Channel, req *ssh.Request, ctx *ctx) error {
ctx.Infof("joinShell(sid=%v)", sid)
@ -204,7 +210,6 @@ func newSession(id string, r *sessionRegistry, context *ctx) (*session, error) {
login: context.login,
closeC: make(chan bool),
}
go sess.pollAndSyncTerm()
return sess, nil
}
@ -229,7 +234,7 @@ func (s *session) upsertSessionParty(sid string, p *party) error {
return s.registry.srv.sessionServer.UpsertParty(sid, rsession.Party{
ID: p.id,
User: p.user,
ServerAddr: p.serverAddr,
ServerID: p.serverID,
RemoteAddr: p.site,
LastActive: p.getLastActive(),
}, rsession.DefaultActivePartyTTL)
@ -245,7 +250,6 @@ func setCmdUser(cmd *exec.Cmd, username string) error {
if err != nil {
return trace.Wrap(err)
}
cmd.Env = []string{"HOME=" + osUser.HomeDir}
// are we already username?
curUser, err := user.Current()
@ -282,6 +286,7 @@ func (s *session) start(sconn *ssh.ServerConn, ch ssh.Channel, ctx *ctx) error {
return trace.Wrap(err)
}
}
go s.pollAndSyncTerm()
cmd := exec.Command(s.registry.srv.shell)
// TODO(klizhentas) figure out linux user policy for launching shells,
// what user and environment should we use to execute the shell? the simplest
@ -304,7 +309,7 @@ func (s *session) start(sconn *ssh.ServerConn, ch ssh.Channel, ctx *ctx) error {
p.ctx.Infof("starting shell input/output streaming")
if s.registry.srv.rec != nil {
w, err := newChunkWriter(s.id, s.registry.srv.rec, s.registry.srv.addr.Addr)
w, err := newChunkWriter(s.id, s.registry.srv.rec, s.registry.srv.ID())
if err != nil {
p.ctx.Errorf("failed to create recorder: %v", err)
return trace.Wrap(err)
@ -318,7 +323,10 @@ func (s *session) start(sconn *ssh.ServerConn, ch ssh.Channel, ctx *ctx) error {
s.addParty(p)
// Pipe session to shell and visa-versa capturing input and output
s.term.Add(1)
go func() {
// notify terminal about a copy process going on
defer s.term.Add(-1)
written, err := io.Copy(s.writer, s.term.pty)
p.ctx.Infof("shell to channel copy closed, bytes written: %v, err: %v",
written, err)
@ -415,7 +423,9 @@ func (s *session) addParty(p *party) {
s.parties[p.id] = p
s.writer.addWriter(p.id, p, true)
p.ctx.addCloser(p)
s.term.Add(1)
go func() {
defer s.term.Add(-1)
written, err := io.Copy(s.term.pty, p)
p.ctx.Infof("channel to shell copy closed, bytes written: %v, err: %v",
written, err)
@ -522,7 +532,7 @@ func (m *multiWriter) Write(p []byte) (n int, err error) {
func newParty(s *session, sconn *ssh.ServerConn, ch ssh.Channel, ctx *ctx) *party {
return &party{
user: ctx.teleportUser,
serverAddr: s.registry.srv.addr.Addr,
serverID: s.registry.srv.ID(),
site: sconn.RemoteAddr().String(),
id: uuid.New(),
sconn: sconn,
@ -536,7 +546,7 @@ func newParty(s *session, sconn *ssh.ServerConn, ch ssh.Channel, ctx *ctx) *part
type party struct {
sync.Mutex
user string
serverAddr string
serverID string
site string
id string
s *session
@ -578,7 +588,7 @@ func (p *party) Close() error {
return p.s.registry.leaveShell(p.s.id, p.id)
}
func newChunkWriter(sessionID string, rec recorder.Recorder, serverAddr string) (*chunkWriter, error) {
func newChunkWriter(sessionID string, rec recorder.Recorder, serverID string) (*chunkWriter, error) {
cw, err := rec.GetChunkWriter(sessionID)
if err != nil {
return nil, err
@ -586,7 +596,7 @@ func newChunkWriter(sessionID string, rec recorder.Recorder, serverAddr string)
return &chunkWriter{
w: cw,
rid: sessionID,
serverAddr: serverAddr,
serverID: serverID,
}, nil
}
@ -594,7 +604,7 @@ type chunkWriter struct {
before time.Time
rid string
w recorder.ChunkWriteCloser
serverAddr string
serverID string
}
func (l *chunkWriter) Write(b []byte) (int, error) {
@ -607,7 +617,7 @@ func (l *chunkWriter) Write(b []byte) (int, error) {
l.before = now
}
cs := []recorder.Chunk{
recorder.Chunk{Delay: diff, Data: b, ServerAddr: l.serverAddr},
recorder.Chunk{Delay: diff, Data: b, ServerID: l.serverID},
}
if err := l.w.WriteChunks(cs); err != nil {
return 0, err

View file

@ -44,7 +44,6 @@ import (
"github.com/gravitational/teleport/lib/sshutils"
"github.com/gravitational/teleport/lib/utils"
"github.com/gokyle/hotp"
"github.com/gravitational/trace"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/agent"
@ -401,20 +400,8 @@ func (s *SrvSuite) TestProxy(c *C) {
c.Assert(err, IsNil)
c.Assert(tsrv.Start(), IsNil)
user := "user1"
pass := []byte("utndkrn")
hotpURL, _, err := s.a.UpsertPassword(user, pass)
c.Assert(err, IsNil)
otp, _, err := hotp.FromURL(hotpURL)
c.Assert(err, IsNil)
otp.Increment()
authMethod, err := auth.NewWebPasswordAuth(user, pass, otp.OTP())
c.Assert(err, IsNil)
tunClt, err := auth.NewTunClient(
utils.NetAddr{AddrNetwork: "tcp", Addr: tsrv.Addr()}, user, authMethod)
utils.NetAddr{AddrNetwork: "tcp", Addr: tsrv.Addr()}, s.domainName, []ssh.AuthMethod{ssh.PublicKeys(s.signer)})
c.Assert(err, IsNil)
defer tunClt.Close()
@ -606,20 +593,8 @@ func (s *SrvSuite) TestProxyRoundRobin(c *C) {
c.Assert(err, IsNil)
c.Assert(tsrv.Start(), IsNil)
user := "user1"
pass := []byte("utndkrn")
hotpURL, _, err := s.a.UpsertPassword(user, pass)
c.Assert(err, IsNil)
otp, _, err := hotp.FromURL(hotpURL)
c.Assert(err, IsNil)
otp.Increment()
authMethod, err := auth.NewWebPasswordAuth(user, pass, otp.OTP())
c.Assert(err, IsNil)
tunClt, err := auth.NewTunClient(
utils.NetAddr{AddrNetwork: "tcp", Addr: tsrv.Addr()}, user, authMethod)
utils.NetAddr{AddrNetwork: "tcp", Addr: tsrv.Addr()}, s.domainName, []ssh.AuthMethod{ssh.PublicKeys(s.signer)})
c.Assert(err, IsNil)
defer tunClt.Close()
@ -716,20 +691,8 @@ func (s *SrvSuite) TestProxyDirectAccess(c *C) {
c.Assert(err, IsNil)
c.Assert(tsrv.Start(), IsNil)
user := "user1"
pass := []byte("utndkrn")
hotpURL, _, err := s.a.UpsertPassword(user, pass)
c.Assert(err, IsNil)
otp, _, err := hotp.FromURL(hotpURL)
c.Assert(err, IsNil)
otp.Increment()
authMethod, err := auth.NewWebPasswordAuth(user, pass, otp.OTP())
c.Assert(err, IsNil)
tunClt, err := auth.NewTunClient(
utils.NetAddr{AddrNetwork: "tcp", Addr: tsrv.Addr()}, user, authMethod)
utils.NetAddr{AddrNetwork: "tcp", Addr: tsrv.Addr()}, s.domainName, []ssh.AuthMethod{ssh.PublicKeys(s.signer)})
c.Assert(err, IsNil)
defer tunClt.Close()

View file

@ -19,7 +19,9 @@ package srv
import (
"os"
"os/exec"
"sync"
"github.com/gravitational/teleport"
rsession "github.com/gravitational/teleport/lib/session"
"github.com/gravitational/teleport/lib/sshutils"
@ -33,6 +35,8 @@ import (
// terminal provides handy functions for managing PTY, usch as resizing windows
// execing processes with PTY and cleaning up
type terminal struct {
sync.WaitGroup
sync.Mutex
pty *os.File
tty *os.File
err error
@ -79,6 +83,12 @@ func requestPTY(req *ssh.Request) (*terminal, *rsession.TerminalParams, error) {
}
func (t *terminal) getWinsize() (*term.Winsize, error) {
t.Lock()
defer t.Unlock()
if t.pty == nil {
return nil, trace.Wrap(teleport.NotFound("no pty"))
}
log.Infof("pty: %v", t.pty)
ws, err := term.GetWinsize(t.pty.Fd())
if err != nil {
return nil, trace.Wrap(err)
@ -87,6 +97,11 @@ func (t *terminal) getWinsize() (*term.Winsize, error) {
}
func (t *terminal) setWinsize(params rsession.TerminalParams) error {
t.Lock()
defer t.Unlock()
if t.pty == nil {
return trace.Wrap(teleport.NotFound("no pty"))
}
log.Infof("window resize %v", &params)
return trace.Wrap(term.SetWinsize(t.pty.Fd(), params.Winsize()))
}
@ -110,17 +125,30 @@ func (t *terminal) run(c *exec.Cmd) error {
func (t *terminal) Close() error {
var err error
if e := t.pty.Close(); e != nil {
err = e
}
// note, pty is closed in the copying goroutine,
// not here to avoid data races
if t.tty != nil {
if e := t.tty.Close(); e != nil {
err = e
}
}
go t.closePTY()
return trace.Wrap(err)
}
func (t *terminal) closePTY() {
t.Lock()
defer t.Unlock()
// wait until all copying is over
log.Infof("Terminal wait for copy to be over")
t.Wait()
log.Infof("Terminal copy is over")
t.pty.Close()
t.pty = nil
}
func parseWinChange(req *ssh.Request) (*rsession.TerminalParams, error) {
var r sshutils.WinChangeReqParams
if err := ssh.Unmarshal(req.Payload, &r); err != nil {

View file

@ -54,7 +54,7 @@ func (s *SCPSuite) TestSendFile(c *C) {
c.Assert(err, IsNil)
outDir := c.MkDir()
cmd, in, out, epipe := command("scp", "-v", "-t", outDir)
cmd, in, out, _ := command("scp", "-v", "-t", outDir)
errC := make(chan error, 2)
successC := make(chan bool)
@ -74,12 +74,6 @@ func (s *SCPSuite) TestSendFile(c *C) {
close(successC)
}()
go func() {
for {
io.Copy(log.StandardLogger().Out, epipe)
}
}()
select {
case <-time.After(2 * time.Second):
c.Fatalf("timeout")
@ -106,7 +100,7 @@ func (s *SCPSuite) TestReceiveFile(c *C) {
srv, err := New(Command{Sink: true, Target: outDir})
c.Assert(err, IsNil)
cmd, in, out, epipe := command("scp", "-v", "-f", source)
cmd, in, out, _ := command("scp", "-v", "-f", source)
errC := make(chan error, 3)
successC := make(chan bool, 1)
@ -131,12 +125,6 @@ func (s *SCPSuite) TestReceiveFile(c *C) {
successC <- true
}()
go func() {
for {
io.Copy(log.StandardLogger().Out, epipe)
}
}()
select {
case <-time.After(time.Second):
c.Fatalf("timeout waiting for results")
@ -168,7 +156,7 @@ func (s *SCPSuite) TestSendDir(c *C) {
outDir := c.MkDir()
cmd, in, out, epipe := command("scp", "-v", "-r", "-t", outDir)
cmd, in, out, _ := command("scp", "-v", "-r", "-t", outDir)
errC := make(chan error, 2)
successC := make(chan bool)
@ -188,12 +176,6 @@ func (s *SCPSuite) TestSendDir(c *C) {
close(successC)
}()
go func() {
for {
io.Copy(log.StandardLogger().Out, epipe)
}
}()
select {
case <-time.After(time.Second):
panic("timeout")
@ -231,7 +213,7 @@ func (s *SCPSuite) TestReceiveDir(c *C) {
srv, err := New(Command{Sink: true, Target: outDir, Recursive: true})
c.Assert(err, IsNil)
cmd, in, out, epipe := command("scp", "-v", "-r", "-f", dir)
cmd, in, out, _ := command("scp", "-v", "-r", "-f", dir)
errC := make(chan error, 2)
successC := make(chan bool)
@ -248,12 +230,6 @@ func (s *SCPSuite) TestReceiveDir(c *C) {
close(successC)
}()
go func() {
for {
io.Copy(log.StandardLogger().Out, epipe)
}
}()
select {
case <-time.After(time.Second):
c.Fatalf("timeout")

View file

@ -13,6 +13,7 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package utils
import (
@ -23,10 +24,10 @@ import (
)
type HangoutInfo struct {
AuthPort string
NodePort string
HangoutID string
OSUser string
AuthPort string `json:"auth_port"`
NodePort string `json:"node_port"`
HangoutID string `json:"hangout_id"`
OSUser string `json:"os_user"`
}
func MarshalHangoutInfo(h *HangoutInfo) (string, error) {

View file

@ -18,11 +18,11 @@ package web
import (
"fmt"
"net"
"net/http"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/lib/reversetunnel"
"github.com/gravitational/teleport/lib/services"
"github.com/gravitational/teleport/lib/session"
"github.com/gravitational/teleport/lib/sshutils"
@ -35,8 +35,8 @@ import (
// connectReq is a request to open interactive SSH
// connection to remote server
type connectReq struct {
// Addr is a host:port pair of the server to connect to
Addr string `json:"addr"`
// ServerID is a server id to connect to
ServerID string `json:"server_id"`
// User is linux username to connect as
Login string `json:"login"`
// Term sets PTY params like width and height
@ -46,8 +46,25 @@ type connectReq struct {
}
func newConnectHandler(req connectReq, ctx *sessionContext, site reversetunnel.RemoteSite) (*connectHandler, error) {
if _, _, err := net.SplitHostPort(req.Addr); err != nil {
return nil, trace.Wrap(teleport.BadParameter("addr", "bad address format"))
clt, err := site.GetClient()
if err != nil {
return nil, trace.Wrap(err)
}
servers, err := clt.GetServers()
if err != nil {
return nil, trace.Wrap(err)
}
var server *services.Server
for i := range servers {
node := servers[i]
if node.ID == req.ServerID {
server = &node
}
}
if server == nil {
return nil, trace.Wrap(
teleport.NotFound(
fmt.Sprintf("node '%v' not found", req.ServerID)))
}
if req.Login == "" {
return nil, trace.Wrap(teleport.BadParameter("login", "missing login"))
@ -59,6 +76,7 @@ func newConnectHandler(req connectReq, ctx *sessionContext, site reversetunnel.R
req: req,
ctx: ctx,
site: site,
server: *server,
}, nil
}
@ -69,6 +87,7 @@ type connectHandler struct {
up *sshutils.Upstream
req connectReq
ws *websocket.Conn
server services.Server
}
func (w *connectHandler) String() string {
@ -76,7 +95,9 @@ func (w *connectHandler) String() string {
}
func (w *connectHandler) Close() error {
if w.ws != nil {
w.ws.Close()
}
if w.up != nil {
return w.up.Close()
}
@ -107,11 +128,17 @@ func (w *connectHandler) resizePTYWindow(params session.TerminalParams) error {
}
func (w *connectHandler) connectUpstream() (*sshutils.Upstream, error) {
methods, err := w.ctx.GetAuthMethods()
agent, err := w.ctx.GetAgent()
if err != nil {
return nil, trace.Wrap(err)
}
client, err := w.site.ConnectToServer(w.req.Addr, w.req.Login, methods)
defer agent.Close()
signers, err := agent.Signers()
if err != nil {
return nil, trace.Wrap(err)
}
client, err := w.site.ConnectToServer(
w.server.Addr, w.req.Login, []ssh.AuthMethod{ssh.PublicKeys(signers...)})
if err != nil {
return nil, trace.Wrap(err)
}

View file

@ -31,7 +31,6 @@ import (
log "github.com/Sirupsen/logrus"
"github.com/gravitational/trace"
"github.com/mailgun/ttlmap"
"golang.org/x/crypto/ssh"
)
// sessionContext is a context associated with users'
@ -116,18 +115,14 @@ func (c *sessionContext) CreateWebSession() (*auth.Session, error) {
return sess, nil
}
// GetAuthMethods returns authentication methods (credentials) that proxy
// can use to connect to servers
func (c *sessionContext) GetAuthMethods() ([]ssh.AuthMethod, error) {
// GetAgent returns agent that can we used to answer challenges
// for the web to ssh connection
func (c *sessionContext) GetAgent() (auth.AgentCloser, error) {
a, err := c.clt.GetAgent()
if err != nil {
return nil, trace.Wrap(err)
}
signers, err := a.Signers()
if err != nil {
return nil, trace.Wrap(err)
}
return []ssh.AuthMethod{ssh.PublicKeys(signers...)}, nil
return a, nil
}
// Close cleans up connections associated with requests

View file

@ -409,18 +409,19 @@ func (m *Handler) getSiteNodes(w http.ResponseWriter, r *http.Request, _ httprou
return nil, trace.Wrap(err)
}
sessions, err := clt.GetSessions()
log.Infof("sessoins: %v", sessions)
if err != nil {
return nil, trace.Wrap(err)
}
nodeMap := make(map[string]*nodeWithSessions, len(servers))
for i := range servers {
nodeMap[servers[i].Addr] = &nodeWithSessions{Node: servers[i]}
nodeMap[servers[i].ID] = &nodeWithSessions{Node: servers[i]}
}
for i := range sessions {
sess := sessions[i]
for _, p := range sess.Parties {
if _, ok := nodeMap[p.ServerAddr]; ok {
nodeMap[p.ServerAddr].Sessions = append(nodeMap[p.ServerAddr].Sessions, sess)
if _, ok := nodeMap[p.ServerID]; ok {
nodeMap[p.ServerID].Sessions = append(nodeMap[p.ServerID].Sessions, sess)
}
}
}
@ -440,7 +441,7 @@ func (m *Handler) getSiteNodes(w http.ResponseWriter, r *http.Request, _ httprou
// Due to the nature of websocket we can't POST parameters as is, so we have
// to add query parameters. The params query parameter is a url encodeed JSON strucrture:
//
// {"addr": "127.0.0.1:5000", "login": "admin", "term": {"h": 120, "w": 100}, "sid": "123"}
// {"server_id": "uuid", "login": "admin", "term": {"h": 120, "w": 100}, "sid": "123"}
//
// Session id can be empty
//

View file

@ -63,6 +63,7 @@ func TestWeb(t *testing.T) { TestingT(t) }
type WebSuite struct {
node *srv.Server
srvAddress string
srvID string
srvHostPort string
bk *encryptedbk.ReplicatedBackend
roleAuth *auth.AuthWithRoles
@ -152,6 +153,7 @@ func (s *WebSuite) SetUpTest(c *C) {
)
c.Assert(err, IsNil)
s.node = node
s.srvID = node.ID()
c.Assert(s.node.Start(), IsNil)
@ -490,7 +492,7 @@ func (s *WebSuite) connect(c *C, pack *authPack, opts ...string) *websocket.Conn
}
u := url.URL{Host: s.url().Host, Scheme: WSS, Path: fmt.Sprintf("/v1/webapi/sites/%v/connect", currentSiteShortcut)}
data, err := json.Marshal(connectReq{
Addr: s.srvAddress,
ServerID: s.srvID,
Login: s.user,
Term: session.TerminalParams{W: 100, H: 100},
SessionID: sessionID,

View file

@ -47,7 +47,7 @@ type TextFormatter struct {
func (tf *TextFormatter) Format(e *log.Entry) ([]byte, error) {
if frameNo := findFrame(); frameNo != -1 {
t := newTrace(runtime.Caller(frameNo - 1))
e.Data[FileField] = t.String()
e = e.WithFields(log.Fields{FileField: t.String()})
}
return (&tf.TextFormatter).Format(e)
}
@ -62,8 +62,10 @@ type JSONFormatter struct {
func (j *JSONFormatter) Format(e *log.Entry) ([]byte, error) {
if frameNo := findFrame(); frameNo != -1 {
t := newTrace(runtime.Caller(frameNo - 1))
e.Data[FileField] = t.String()
e.Data[FunctionField] = t.Func
e = e.WithFields(log.Fields{
FileField: t.String(),
FunctionField: t.Func,
})
}
return (&j.JSONFormatter).Format(e)
}

1164
web/dist/app/app.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1855
web/dist/app/vendor.js vendored

File diff suppressed because one or more lines are too long

6
web/dist/index.html vendored
View file

@ -11,11 +11,11 @@
<script src="/web/app/assets/js/underscore-1.8.3.js"></script>
<script src="/web/app/assets/js/bootstrap.js"></script>
<script src="/web/app/assets/js/term.js"></script>
<script src="/web/app/vendor.js?ver=0.11456968818190"></script>
<script src="/web/app/styles.js?ver=0.11456968818190"></script>
<script src="/web/app/vendor.js?ver=0.11457083126376"></script>
<script src="/web/app/styles.js?ver=0.11457083126376"></script>
</head>
<body class="grv">
<div id="app"></div>
</body>
<script src="/web/app/app.js?ver=0.11456968818190"></script>
<script src="/web/app/app.js?ver=0.11457083126376"></script>
</html>

View file

@ -5,9 +5,9 @@ var {actions} = require('app/modules/activeTerminal/');
class Tty extends EventEmitter {
constructor({addr, login, sid, rows, cols }){
constructor({serverId, login, sid, rows, cols }){
super();
this.options = { addr, login, sid, rows, cols };
this.options = { serverId, login, sid, rows, cols };
this.socket = null;
}

View file

@ -3,8 +3,10 @@ var {getters, actions} = require('app/modules/activeTerminal/');
var EventStreamer = require('./eventStreamer.jsx');
var Tty = require('app/common/tty');
var TtyTerminal = require('./../terminal.jsx');
var NotFoundPage = require('app/components/notFoundPage.jsx');
var ActiveSession = React.createClass({
var ActiveSessionHost = React.createClass({
mixins: [reactor.ReactMixin],
@ -14,18 +16,34 @@ var ActiveSession = React.createClass({
}
},
componentDidMount(){
var { sid } = this.props.params;
if(!this.state.activeSession){
actions.openSession(sid);
}
},
render: function() {
if(!this.state.activeSession){
return null;
}
return <ActiveSession activeSession={this.state.activeSession}/>;
}
});
var ActiveSession = React.createClass({
render: function() {
return (
<div className="grv-terminal-host">
<div className="grv-terminal-participans">
<ul className="nav">
{/*
<li><button className="btn btn-primary btn-circle" type="button"> <strong>A</strong></button></li>
<li><button className="btn btn-primary btn-circle" type="button"> B </button></li>
<li><button className="btn btn-primary btn-circle" type="button"> C </button></li>
*/}
<li>
<button onClick={actions.close} className="btn btn-danger btn-circle" type="button">
<i className="fa fa-times"></i>
@ -34,7 +52,7 @@ var ActiveSession = React.createClass({
</ul>
</div>
<div>
<div className="btn-group">
{/*<div className="btn-group">
<span className="btn btn-xs btn-primary">128.0.0.1:8888</span>
<div className="btn-group">
<button data-toggle="dropdown" className="btn btn-default btn-xs dropdown-toggle" aria-expanded="true">
@ -45,9 +63,9 @@ var ActiveSession = React.createClass({
<li><a href="#" target="_blank">Logs</a></li>
</ul>
</div>
</div>*/}
</div>
</div>
<TtyConnection {...this.state.activeSession} />
<TtyConnection {...this.props.activeSession} />
</div>
);
}
@ -66,14 +84,14 @@ var TtyConnection = React.createClass({
},
render() {
let component = new React.Component();
return (
<component>
<div style={{height: '100%'}}>
<TtyTerminal tty={this.tty} cols={this.props.cols} rows={this.props.rows} />
{ this.state.isConnected ? <EventStreamer sid={this.props.sid}/> : null }
</component>
</div>
)
}
});
export {ActiveSession, TtyConnection};
module.exports = {ActiveSession, ActiveSessionHost};

View file

@ -1,20 +1,37 @@
var React = require('react');
var NavLeftBar = require('./navLeftBar');
var cfg = require('app/config');
var actions = require('app/modules/actions');
var {ActiveSession} = require('./activeSession/main.jsx');
var reactor = require('app/reactor');
var {actions, getters} = require('app/modules/app');
var App = React.createClass({
componentDidMount(){
actions.fetchNodesAndSessions();
mixins: [reactor.ReactMixin],
getDataBindings() {
return {
app: getters.appState
}
},
componentWillMount(){
actions.initApp();
this.refreshInterval = setInterval(actions.fetchNodesAndSessions, 3000);
},
componentWillUnmount: function() {
clearInterval(this.refreshInterval);
},
render: function() {
if(this.state.app.isInitializing){
return null;
}
return (
<div className="grv-tlpt">
<NavLeftBar/>
<ActiveSession/>
{this.props.activeSessionHost}
<div className="row">
<nav className="" role="navigation" style={{ marginBottom: 0, float: "right" }}>
<ul className="nav navbar-top-links navbar-right">

View file

@ -3,4 +3,5 @@ module.exports.Login = require('./login.jsx');
module.exports.NewUser = require('./newUser.jsx');
module.exports.Nodes = require('./nodes/main.jsx');
module.exports.Sessions = require('./sessions/main.jsx');
module.exports.ActiveSession = require('./activeSession/main.jsx');
module.exports.ActiveSessionHost = require('./activeSession/main.jsx').ActiveSessionHost;
module.exports.NotFoundPage = require('./notFoundPage.jsx');

View file

@ -3,7 +3,9 @@ var $ = require('jQuery');
var reactor = require('app/reactor');
var LinkedStateMixin = require('react-addons-linked-state-mixin');
var {actions} = require('app/modules/user');
var GoogleAuthInfo = require('./googleAuth');
var GoogleAuthInfo = require('./googleAuthLogo');
var cfg = require('app/config');
var LoginInputForm = React.createClass({
mixins: [LinkedStateMixin],
@ -19,7 +21,7 @@ var LoginInputForm = React.createClass({
onClick: function(e) {
e.preventDefault();
if (this.isValid()) {
actions.login({ ...this.state}, '/web');
this.props.onClick(this.state);
}
},
@ -55,20 +57,29 @@ var Login = React.createClass({
getDataBindings() {
return {
// userRequest: getters.userRequest
}
},
render: function() {
onClick(inputData){
var loc = this.props.location;
var redirect = cfg.routes.app;
if(loc.state && loc.state.redirectTo){
redirect = loc.state.redirectTo;
}
actions.login(inputData, redirect);
},
render() {
var isProcessing = false;//this.state.userRequest.get('isLoading');
var isError = false;//this.state.userRequest.get('isError');
return (
<div className="grv-login text-center">
<div className="grv-logo-tprt"></div>
<div className="grv-content grv-flex">
<div className="grv-flex-column">
<LoginInputForm/>
<LoginInputForm onClick={this.onClick}/>
<GoogleAuthInfo/>
<div className="grv-login-info">
<i className="fa fa-question"></i>

View file

@ -4,7 +4,7 @@ var reactor = require('app/reactor');
var {actions, getters} = require('app/modules/invite');
var userModule = require('app/modules/user');
var LinkedStateMixin = require('react-addons-linked-state-mixin');
var GoogleAuthInfo = require('./googleAuth');
var GoogleAuthInfo = require('./googleAuthLogo');
var InviteInputForm = React.createClass({

View file

@ -3,7 +3,7 @@ var reactor = require('app/reactor');
var {getters, actions} = require('app/modules/nodes');
var userGetters = require('app/modules/user/getters');
var {Table, Column, Cell} = require('app/components/table.jsx');
var {open} = require('app/modules/activeTerminal/actions');
var {createNewSession} = require('app/modules/activeTerminal/actions');
const TextCell = ({rowIndex, data, columnKey, ...props}) => (
<Cell {...props}>
@ -27,28 +27,33 @@ const LoginCell = ({user, rowIndex, data, ...props}) => {
return <Cell {...props} />;
}
var serverId = data[rowIndex].id;
var $lis = [];
function onNewSessionClick(i){
var login = user.logins[i];
return () => createNewSession(serverId, login);
}
for(var i = 0; i < user.logins.length; i++){
$lis.push(<li key={i}><a href="#" target="_blank" onClick={open.bind(null, data[rowIndex].addr, user.logins[i], undefined)}>{user.logins[i]}</a></li>);
$lis.push(<li key={i}><a onClick={onNewSessionClick(i)}>{user.logins[i]}</a></li>);
}
return (
<Cell {...props}>
<div className="btn-group">
<button type="button" onClick={open.bind(null, data[rowIndex].addr, user.logins[0], undefined)} className="btn btn-sm btn-primary">{user.logins[0]}</button>
<button type="button" onClick={onNewSessionClick(0)} className="btn btn-sm btn-primary">{user.logins[0]}</button>
{
$lis.length > 1 ? (
<div className="btn-group">
<button data-toggle="dropdown" className="btn btn-default btn-sm dropdown-toggle" aria-expanded="true">
[
<button key={0} data-toggle="dropdown" className="btn btn-default btn-sm dropdown-toggle" aria-expanded="true">
<span className="caret"></span>
</button>
<ul className="dropdown-menu">
<li><a href="#" target="_blank">Logs</a></li>
<li><a href="#" target="_blank">Logs</a></li>
</button>,
<ul key={1} className="dropdown-menu">
{$lis}
</ul>
</div>
): null
] )
: null
}
</div>
</Cell>

View file

@ -0,0 +1,19 @@
var React = require('react');
var NotFoundPage = React.createClass({
render() {
return (
<div className="grv-page-notfound">
<div className="grv-logo-tprt">Teleport</div>
<div className="grv-warning"><i className="fa fa-warning"></i> </div>
<h1>Whoops, we cannot find that</h1>
<div>Looks like the page you are looking for isn't here any longer</div>
<div>If you believe this is an error, please contact your organization administrator.</div>
<div className="contact-section">If you believe this is an issue with Teleport, please <a href="https://github.com/gravitational/teleport/issues/new">create a GitHub issue.</a>
</div>
</div>
);
}
})
module.exports = NotFoundPage;

View file

@ -1,5 +1,6 @@
var React = require('react');
var reactor = require('app/reactor');
var { Link } = require('react-router');
var {Table, Column, Cell, TextCell} = require('app/components/table.jsx');
var {getters} = require('app/modules/sessions');
var {open} = require('app/modules/activeTerminal/actions');
@ -19,23 +20,15 @@ const UsersCell = ({ rowIndex, data, ...props }) => {
};
const ButtonCell = ({ rowIndex, data, ...props }) => {
let onClick = () => {
var rowData = data[rowIndex];
var {sid, addr} = rowData
var login = rowData.login;
open(addr, login, sid);
}
var sessionUrl = data[rowIndex].sessionUrl;
return (
<Cell {...props}>
<button onClick={onClick} className="btn btn-info btn-circle" type="button">
<Link to={sessionUrl} className="btn btn-info btn-circle" type="button">
<i className="fa fa-terminal"></i>
</button>
</Link>
<button className="btn btn-info btn-circle" type="button">
<i className="fa fa-play-circle"></i>
</button>
</Cell>
)
}
@ -71,13 +64,13 @@ var SessionList = React.createClass({
}
/>
<Column
columnKey="addr"
columnKey="serverIp"
header={<Cell> Node </Cell> }
cell={<TextCell data={data} /> }
/>
<Column
columnKey="addr"
columnKey="serverId"
header={<Cell> Users </Cell> }
cell={<UsersCell data={data} /> }
/>

View file

@ -34,7 +34,7 @@ var TtyTerminal = React.createClass({
this.term.open(this.refs.container);
this.term.on('data', (data) => this.tty.send(data));
this.resize(this.rows, this.cols);
this.resize(this.cols, this.rows);
this.tty.on('open', ()=> this.term.write(CONNECTED_TXT));
this.tty.on('close', ()=> this.term.write(DISCONNECT_TXT));
@ -57,7 +57,7 @@ var TtyTerminal = React.createClass({
}
if(rows !== this.rows || cols !== this.cols){
this.resize(rows, cols)
this.resize(cols, rows)
}
return false;

View file

@ -8,10 +8,15 @@ let cfg = {
renewTokenPath:'/v1/webapi/sessions/renew',
nodesPath: '/v1/webapi/sites/-current-/nodes',
sessionPath: '/v1/webapi/sessions',
fetchSessionPath: '/v1/webapi/sites/-current-/sessions/:sid',
terminalSessionPath: '/v1/webapi/sites/-current-/sessions/:sid',
invitePath: '/v1/webapi/users/invites/:inviteToken',
createUserPath: '/v1/webapi/users',
getFetchSessionUrl: (sid)=>{
return formatPattern(cfg.api.fetchSessionPath, {sid});
},
getTerminalSessionUrl: (sid)=> {
return formatPattern(cfg.api.terminalSessionPath, {sid});
},
@ -25,9 +30,9 @@ let cfg = {
return `${hostname}/v1/webapi/sites/-current-/sessions/${sid}/events/stream?access_token=${token}`;
},
getTtyConnStr: ({token, addr, login, sid, rows, cols}) => {
getTtyConnStr: ({token, serverId, login, sid, rows, cols}) => {
var params = {
addr,
server_id: serverId,
login,
sid,
term: {
@ -48,11 +53,15 @@ let cfg = {
logout: '/web/logout',
login: '/web/login',
nodes: '/web/nodes',
activeSession: '/web/active-session/:sid',
activeSession: '/web/sessions/:sid',
newUser: '/web/newuser/:inviteToken',
sessions: '/web/sessions'
}
sessions: '/web/sessions',
pageNotFound: '/web/notfound'
},
getActiveSessionRouteUrl(sid){
return formatPattern(cfg.routes.activeSession, {sid});
}
}
export default cfg;

View file

@ -1,7 +1,7 @@
var React = require('react');
var render = require('react-dom').render;
var { Router, Route, Redirect, IndexRoute, browserHistory } = require('react-router');
var { App, Login, Nodes, Sessions, NewUser, ActiveSession } = require('./components');
var { App, Login, Nodes, Sessions, NewUser, ActiveSessionHost, NotFoundPage } = require('./components');
var {ensureUser} = require('./modules/user/actions');
var auth = require('./auth');
var session = require('./session');
@ -21,10 +21,12 @@ render((
<Route path={cfg.routes.login} component={Login}/>
<Route path={cfg.routes.logout} onEnter={handleLogout}/>
<Route path={cfg.routes.newUser} component={NewUser}/>
<Redirect from={cfg.routes.app} to={cfg.routes.nodes}/>
<Route path={cfg.routes.app} component={App} onEnter={ensureUser} >
<IndexRoute component={Nodes}/>
<Route path={cfg.routes.nodes} component={Nodes}/>
<Route path={cfg.routes.activeSession} components={{activeSessionHost: ActiveSessionHost}}/>
<Route path={cfg.routes.sessions} component={Sessions}/>
</Route>
<Route path="*" component={NotFoundPage} />
</Router>
), document.getElementById("app"));

View file

@ -1,30 +0,0 @@
var reactor = require('app/reactor');
var api = require('app/services/api');
var cfg = require('app/config');
var { TLPT_SESSINS_RECEIVE } = require('./sessions/actionTypes');
var { TLPT_NODES_RECEIVE } = require('./nodes/actionTypes');
export default {
fetchNodesAndSessions(){
api.get(cfg.api.nodesPath).done(json=>{
var nodeArray = [];
var sessions = {};
json.nodes.forEach(item=> {
nodeArray.push(item.node);
if(item.sessions){
item.sessions.forEach(item2=>{
sessions[item2.id] = item2;
})
}
});
reactor.batch(() => {
reactor.dispatch(TLPT_NODES_RECEIVE, nodeArray);
reactor.dispatch(TLPT_SESSINS_RECEIVE, sessions);
});
});
}
}

View file

@ -2,7 +2,5 @@ import keyMirror from 'keymirror'
export default keyMirror({
TLPT_TERM_OPEN: null,
TLPT_TERM_CLOSE: null,
TLPT_TERM_CONNECTED: null,
TLPT_TERM_RECEIVE_PARTIES: null
TLPT_TERM_CLOSE: null
})

View file

@ -1,51 +1,76 @@
var reactor = require('app/reactor');
var session = require('app/session');
var {uuid} = require('app/utils');
var api = require('app/services/api');
var cfg = require('app/config');
var invariant = require('invariant');
var getters = require('./getters');
var sessionModule = require('./../sessions');
var { TLPT_TERM_OPEN, TLPT_TERM_CLOSE, TLPT_TERM_CONNECTED, TLPT_TERM_RECEIVE_PARTIES } = require('./actionTypes');
var { TLPT_TERM_OPEN, TLPT_TERM_CLOSE } = require('./actionTypes');
export default {
var actions = {
close(){
let {isNewSession} = reactor.evaluate(getters.activeSession);
reactor.dispatch(TLPT_TERM_CLOSE);
if(isNewSession){
session.getHistory().push(cfg.routes.nodes);
}else{
session.getHistory().push(cfg.routes.sessions);
}
},
resize(w, h){
invariant(w > 5 || h > 5, 'invalid resize parameters');
// some min values
w = w < 5 ? 5 : w;
h = h < 5 ? 5 : h;
let reqData = { terminal_params: { w, h } };
let {sid} = reactor.evaluate(getters.activeSession);
api.put(cfg.api.getTerminalSessionUrl(sid), reqData).done(()=>{
console.log(`resize with ${w} and ${h} - OK`);
}).fail(()=>{
console.log(`failed to resize with ${w} and ${h}`);
api.put(cfg.api.getTerminalSessionUrl(sid), reqData)
.done(()=>{
console.log(`resize with w:${w} and h:${h} - OK`);
})
.fail(()=>{
console.log(`failed to resize with w:${w} and h:${h}`);
})
},
connected(){
reactor.dispatch(TLPT_TERM_CONNECTED);
},
receiveParties(json){
var parties = json.map(item=>{
return {
user: item.user,
lastActive: new Date(item.last_active)
}
openSession(sid){
sessionModule.actions.fetchSession(sid)
.done(()=>{
let sView = reactor.evaluate(sessionModule.getters.sessionViewById(sid));
let { serverId, login } = sView;
reactor.dispatch(TLPT_TERM_OPEN, {
serverId,
login,
sid,
isNewSession: false
});
})
.fail(()=>{
session.getHistory().push(cfg.routes.pageNotFound);
})
reactor.dispatch(TLPT_TERM_RECEIVE_PARTIES, parties);
},
open(addr, login, sid){
let isNew = !sid;
if(isNew){
sid = uuid();
createNewSession(serverId, login){
var sid = uuid();
var routeUrl = cfg.getActiveSessionRouteUrl(sid);
var history = session.getHistory();
reactor.dispatch(TLPT_TERM_OPEN, {
serverId,
login,
sid,
isNewSession: true
});
history.push(routeUrl);
}
reactor.dispatch(TLPT_TERM_OPEN, {addr, login, sid, isNew} );
}
}
export default actions;

View file

@ -1,5 +1,5 @@
var { Store, toImmutable } = require('nuclear-js');
var { TLPT_TERM_OPEN, TLPT_TERM_CLOSE, TLPT_TERM_CONNECTED, TLPT_TERM_RECEIVE_PARTIES } = require('./actionTypes');
var { TLPT_TERM_OPEN, TLPT_TERM_CLOSE } = require('./actionTypes');
export default Store({
getInitialState() {
@ -7,30 +7,20 @@ export default Store({
},
initialize() {
this.on(TLPT_TERM_CONNECTED, connected);
this.on(TLPT_TERM_OPEN, setActiveTerminal);
this.on(TLPT_TERM_CLOSE, close);
this.on(TLPT_TERM_RECEIVE_PARTIES, receiveParties);
}
})
function close(){
return toImmutable(null);
}
function receiveParties(state, parties){
return state.set('parties', toImmutable(parties));
}
function setActiveTerminal(state, settings){
function setActiveTerminal(state, {serverId, login, sid, isNewSession} ){
return toImmutable({
isConnecting: true,
...settings
serverId,
login,
sid,
isNewSession
});
}
function connected(state){
return state.set('isConnected', true)
.set('isConnecting', false);
}

View file

@ -6,8 +6,10 @@ const activeSession = [
}
let view = {
isNew: activeTerm.get('isNew'),
isNewSession: activeTerm.get('isNewSession'),
notFound: activeTerm.get('notFound'),
addr: activeTerm.get('addr'),
serverId: activeTerm.get('serverId'),
login: activeTerm.get('login'),
sid: activeTerm.get('sid'),
cols: undefined,
@ -15,8 +17,8 @@ const activeSession = [
};
if(sessions.has(view.sid)){
view.cols = sessions.getIn([view.sid, 'terminal_params', 'H']);
view.rows = sessions.getIn([view.sid, 'terminal_params', 'W']);
view.cols = sessions.getIn([view.sid, 'terminal_params', 'w']);
view.rows = sessions.getIn([view.sid, 'terminal_params', 'h']);
}
return view;

View file

@ -0,0 +1,7 @@
import keyMirror from 'keymirror'
export default keyMirror({
TLPT_APP_INIT: null,
TLPT_APP_FAILED: null,
TLPT_APP_READY: null
})

View file

@ -0,0 +1,43 @@
var reactor = require('app/reactor');
var api = require('app/services/api');
var cfg = require('app/config');
var { TLPT_SESSINS_RECEIVE } = require('./../sessions/actionTypes');
var { TLPT_NODES_RECEIVE } = require('./../nodes/actionTypes');
var { TLPT_APP_INIT, TLPT_APP_FAILED, TLPT_APP_READY } = require('./actionTypes');
export default {
initApp() {
reactor.dispatch(TLPT_APP_INIT);
module.exports.fetchNodesAndSessions()
.done(()=>{
reactor.dispatch(TLPT_APP_READY);
})
.fail(()=>{
reactor.dispatch(TLPT_APP_FAILED);
});
},
fetchNodesAndSessions() {
return api.get(cfg.api.nodesPath).done(json => {
var nodeArray = [];
var sessions = {};
json.nodes.forEach(item => {
nodeArray.push(item.node);
if (item.sessions) {
item.sessions.forEach(item2 => {
sessions[item2.id] = item2;
})
}
});
reactor.batch(() => {
reactor.dispatch(TLPT_NODES_RECEIVE, nodeArray);
reactor.dispatch(TLPT_SESSINS_RECEIVE, sessions);
});
});
}
}

View file

@ -0,0 +1,22 @@
var { Store, toImmutable } = require('nuclear-js');
var { TLPT_APP_INIT, TLPT_APP_FAILED, TLPT_APP_READY } = require('./actionTypes');
var initState = toImmutable({
isReady: false,
isInitializing: false,
isFailed: false
});
export default Store({
getInitialState() {
return initState.set('isInitializing', true);
},
initialize() {
this.on(TLPT_APP_INIT, ()=> initState.set('isInitializing', true));
this.on(TLPT_APP_READY,()=> initState.set('isReady', true));
this.on(TLPT_APP_FAILED,()=> initState.set('isFailed', true));
}
})

View file

@ -0,0 +1,5 @@
const appState = [['tlpt'], app=> app.toJS()];
export default {
appState
}

View file

@ -0,0 +1,3 @@
module.exports.getters = require('./getters');
module.exports.actions = require('./actions');
module.exports.appStore = require('./appStore');

View file

@ -1,5 +1,6 @@
var reactor = require('app/reactor');
reactor.registerStores({
'tlpt': require('./app/appStore'),
'tlpt_active_terminal': require('./activeTerminal/activeTermStore'),
'tlpt_user': require('./user/userStore'),
'tlpt_nodes': require('./nodes/nodeStore'),

View file

@ -3,11 +3,13 @@ var {sessionsByServer} = require('./../sessions/getters');
const nodeListView = [ ['tlpt_nodes'], (nodes) =>{
return nodes.map((item)=>{
var addr = item.get('addr');
var sessions = reactor.evaluate(sessionsByServer(addr));
var serverId = item.get('id');
var sessions = reactor.evaluate(sessionsByServer(serverId));
return {
id: serverId,
hostname: item.get('hostname'),
tags: getTags(item),
addr: addr,
addr: item.get('addr'),
sessionCount: sessions.size
}
}).toJS();

View file

@ -1,7 +1,19 @@
var reactor = require('app/reactor');
var api = require('app/services/api');
var cfg = require('app/config');
var { TLPT_SESSINS_RECEIVE, TLPT_SESSINS_UPDATE } = require('./actionTypes');
export default {
fetchSession(sid){
return api.get(cfg.api.getFetchSessionUrl(sid)).then(json=>{
if(json && json.session){
reactor.dispatch(TLPT_SESSINS_UPDATE, json.session);
}
});
},
updateSession(json){
reactor.dispatch(TLPT_SESSINS_UPDATE, json);
},

View file

@ -1,25 +1,25 @@
var { toImmutable } = require('nuclear-js');
var reactor = require('app/reactor');
var cfg = require('app/config');
const sessionsByServer = (addr) => [['tlpt_sessions'], (sessions) =>{
const sessionsByServer = (serverId) => [['tlpt_sessions'], (sessions) =>{
return sessions.valueSeq().filter(item=>{
var parties = item.get('parties') || toImmutable([]);
var hasServer = parties.find(item2=> item2.get('server_addr') === addr);
var hasServer = parties.find(item2=> item2.get('server_id') === serverId);
return hasServer;
}).toList();
}]
const sessionsView = [['tlpt_sessions'], (sessions) =>{
return sessions.valueSeq().map(item=>{
var sid = item.get('id');
var parties = reactor.evaluate(partiesBySessionId(sid));
return {
sid: sid,
addr: parties[0].addr,
login: item.get('login'),
parties: parties
return sessions.valueSeq().map(createView).toJS();
}];
const sessionViewById = (sid)=> [['tlpt_sessions', sid], (session)=>{
if(!session){
return null;
}
}).toJS();
return createView(session);
}];
const partiesBySessionId = (sid) =>
@ -35,7 +35,8 @@ const partiesBySessionId = (sid) =>
var user = item.get('user');
return {
user: item.get('user'),
addr: item.get('server_addr'),
serverIp: item.get('remote_addr'),
serverId: item.get('server_id'),
isActive: lastActiveUsrName === user
}
}).toJS();
@ -45,8 +46,29 @@ function getLastActiveUser(parties){
return parties.sortBy(item=> new Date(item.get('lastActive'))).first();
}
function createView(session){
var sid = session.get('id');
var serverIp, serverId;
var parties = reactor.evaluate(partiesBySessionId(sid));
if(parties.length > 0){
serverIp = parties[0].serverIp;
serverId = parties[0].serverId;
}
return {
sid: sid,
sessionUrl: cfg.getActiveSessionRouteUrl(sid),
serverIp,
serverId,
login: session.get('login'),
parties: parties
}
}
export default {
partiesBySessionId,
sessionsByServer,
sessionsView
sessionsView,
sessionViewById
}

View file

@ -17,5 +17,5 @@ function updateSession(state, json){
}
function receiveSessions(state, json){
return state.merge(json);
return toImmutable(json);
}

View file

@ -6,6 +6,7 @@
@import "grv-sessions";
@import "grv-nav";
@import "grv-terminal-host";
@import "grv-page-notfound";
.grv {
background-color: white;

View file

@ -9,11 +9,10 @@
> li.active{
border: none;
background: #2f4050;
background-color: #293846;
}
li:hover{
background-color: #2f4050;
}
li:first-child{

View file

@ -0,0 +1,14 @@
.grv-page-notfound{
margin: 0 auto;
max-width: 600px;
text-align: center;
.grv-warning{
font-size: 60px;
}
.contact-section{
margin-top: 20px;
}
}