mirror of
https://github.com/gravitational/teleport
synced 2024-10-22 10:13:21 +00:00
Merge remote-tracking branch 'origin/master' into ev/194
Conflicts: .gitignore web/dist/app/app
This commit is contained in:
commit
9de557a98a
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -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
2
Godeps/Godeps.json
generated
|
@ -88,7 +88,7 @@
|
|||
},
|
||||
{
|
||||
"ImportPath": "github.com/gravitational/trace",
|
||||
"Rev": "af77d5facfcfa8cdccdae73865728c8c0ac3dbf6"
|
||||
"Rev": "7d4c31f3b31deca858e7550117ff11f4b8abc928"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/jonboulle/clockwork",
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
113
lib/auth/tun.go
113
lib/auth/tun.go
|
@ -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)
|
||||
}
|
||||
ch, _, err = t.tun.OpenChannel(ReqWebSessionAgent, nil)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return nil, trace.Wrap(
|
||||
teleport.ConnectionProblem("failed to connect to remote API", 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,32 +590,33 @@ 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)
|
||||
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)
|
||||
log.Infof("TunDialer.Dial(%v, %v)", network, address)
|
||||
client, err := t.getClient()
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(
|
||||
teleport.ConnectionProblem("failed to connect to remote API", 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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
baseBk backend.Backend
|
||||
ebk []*EncryptedBackend
|
||||
mutex *sync.Mutex
|
||||
keyStore KeyStore
|
||||
signKey encryptor.Key
|
||||
signCheckingKeys []encryptor.Key
|
||||
keyGenerator encryptor.KeyGenerator
|
||||
sync.Mutex
|
||||
baseBk backend.Backend
|
||||
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
|
||||
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)
|
||||
|
||||
keyStore, err := NewKeyStore(keysFile)
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
return nil, err
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
backend.AcquireLock(bkLock, time.Minute)
|
||||
defer backend.ReleaseLock(bkLock)
|
||||
|
||||
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 repBk.refreshKeys()
|
||||
|
||||
log.Infof("Backend was initialized")
|
||||
return &repBk, nil
|
||||
go repBackend.refreshKeys()
|
||||
return repBackend, 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,70 +149,70 @@ 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 {
|
||||
|
||||
for _, key := range localKeys {
|
||||
bk, err := newEncryptedBackend(b.baseBk, key,
|
||||
b.signKey, b.signCheckingKeys)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
err = bk.Sign()
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
b.ebk = append(b.ebk, bk)
|
||||
if len(key.PrivateValue) != 0 {
|
||||
if err := bk.VerifySign(); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
if err := b.addSignCheckingKey(key.Public()); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, key := range localKeys {
|
||||
if err := b.upsertKeyToPublicKeysList(key); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
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)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
err = bk.Sign()
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
b.encryptedBackends = append(b.encryptedBackends, bk)
|
||||
if len(key.PrivateValue) != 0 {
|
||||
if err := bk.VerifySign(); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
if err := b.addSignCheckingKey(key.Public()); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
|
||||
for _, key := range localKeys {
|
||||
if err := b.upsertKeyToPublicKeysList(key); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
y := int32(0)
|
||||
c.Assert(s.B.AcquireLock(tok1, 0), IsNil)
|
||||
c.Assert(s.B.AcquireLock(tok2, 0), IsNil)
|
||||
go func() {
|
||||
c.Assert(s.B.AcquireLock(tok1, 0), IsNil)
|
||||
c.Assert(s.B.AcquireLock(tok2, 0), IsNil)
|
||||
|
||||
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{})
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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),
|
||||
})
|
||||
var hangoutInfo *utils.HangoutInfo
|
||||
for _, sess := range sessions {
|
||||
info, err := utils.UnmarshalHangoutInfo(sess.ID)
|
||||
if err != nil {
|
||||
log.Infof("failed to unmarshal hangout info: %v", err)
|
||||
}
|
||||
if info.HangoutID == hangoutID {
|
||||
hangoutInfo = info
|
||||
break
|
||||
}
|
||||
}
|
||||
hangoutInfo, err := utils.UnmarshalHangoutInfo(sess[0].ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
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
|
||||
}
|
||||
|
||||
|
|
|
@ -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{})
|
||||
}
|
||||
|
|
|
@ -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"}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
@ -521,22 +531,22 @@ 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,
|
||||
site: sconn.RemoteAddr().String(),
|
||||
id: uuid.New(),
|
||||
sconn: sconn,
|
||||
ch: ch,
|
||||
ctx: ctx,
|
||||
s: s,
|
||||
closeC: make(chan bool),
|
||||
user: ctx.teleportUser,
|
||||
serverID: s.registry.srv.ID(),
|
||||
site: sconn.RemoteAddr().String(),
|
||||
id: uuid.New(),
|
||||
sconn: sconn,
|
||||
ch: ch,
|
||||
ctx: ctx,
|
||||
s: s,
|
||||
closeC: make(chan bool),
|
||||
}
|
||||
}
|
||||
|
||||
type party struct {
|
||||
sync.Mutex
|
||||
user string
|
||||
serverAddr string
|
||||
serverID string
|
||||
site string
|
||||
id string
|
||||
s *session
|
||||
|
@ -578,23 +588,23 @@ 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
|
||||
}
|
||||
return &chunkWriter{
|
||||
w: cw,
|
||||
rid: sessionID,
|
||||
serverAddr: serverAddr,
|
||||
w: cw,
|
||||
rid: sessionID,
|
||||
serverID: serverID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
type chunkWriter struct {
|
||||
before time.Time
|
||||
rid string
|
||||
w recorder.ChunkWriteCloser
|
||||
serverAddr string
|
||||
before time.Time
|
||||
rid string
|
||||
w recorder.ChunkWriteCloser
|
||||
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
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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", ¶ms)
|
||||
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 {
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"))
|
||||
|
@ -56,19 +73,21 @@ func newConnectHandler(req connectReq, ctx *sessionContext, site reversetunnel.R
|
|||
return nil, trace.Wrap(teleport.BadParameter("term", "bad term dimensions"))
|
||||
}
|
||||
return &connectHandler{
|
||||
req: req,
|
||||
ctx: ctx,
|
||||
site: site,
|
||||
req: req,
|
||||
ctx: ctx,
|
||||
site: site,
|
||||
server: *server,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// connectHandler is a websocket to SSH proxy handler
|
||||
type connectHandler struct {
|
||||
ctx *sessionContext
|
||||
site reversetunnel.RemoteSite
|
||||
up *sshutils.Upstream
|
||||
req connectReq
|
||||
ws *websocket.Conn
|
||||
ctx *sessionContext
|
||||
site reversetunnel.RemoteSite
|
||||
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 {
|
||||
w.ws.Close()
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
//
|
||||
|
|
|
@ -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,
|
||||
|
|
8
vendor/github.com/gravitational/trace/log.go
generated
vendored
8
vendor/github.com/gravitational/trace/log.go
generated
vendored
|
@ -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)
|
||||
}
|
||||
|
|
1172
web/dist/app/app.js
vendored
1172
web/dist/app/app.js
vendored
File diff suppressed because one or more lines are too long
28
web/dist/app/styles.js
vendored
28
web/dist/app/styles.js
vendored
File diff suppressed because one or more lines are too long
1855
web/dist/app/vendor.js
vendored
1855
web/dist/app/vendor.js
vendored
File diff suppressed because one or more lines are too long
6
web/dist/index.html
vendored
6
web/dist/index.html
vendored
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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({
|
||||
|
||||
|
|
|
@ -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">
|
||||
<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>
|
||||
</ul>
|
||||
</div>
|
||||
): null
|
||||
[
|
||||
<button key={0} data-toggle="dropdown" className="btn btn-default btn-sm dropdown-toggle" aria-expanded="true">
|
||||
<span className="caret"></span>
|
||||
</button>,
|
||||
<ul key={1} className="dropdown-menu">
|
||||
{$lis}
|
||||
</ul>
|
||||
] )
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
</Cell>
|
||||
|
@ -65,7 +70,7 @@ var Nodes = React.createClass({
|
|||
user: userGetters.user
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
render: function() {
|
||||
var data = this.state.nodeRecords;
|
||||
return (
|
||||
|
|
19
web/src/app/components/notFoundPage.jsx
Normal file
19
web/src/app/components/notFoundPage.jsx
Normal 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;
|
|
@ -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} /> }
|
||||
/>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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
|
||||
})
|
||||
|
|
|
@ -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);
|
||||
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);
|
||||
})
|
||||
},
|
||||
|
||||
receiveParties(json){
|
||||
var parties = json.map(item=>{
|
||||
return {
|
||||
user: item.user,
|
||||
lastActive: new Date(item.last_active)
|
||||
}
|
||||
})
|
||||
createNewSession(serverId, login){
|
||||
var sid = uuid();
|
||||
var routeUrl = cfg.getActiveSessionRouteUrl(sid);
|
||||
var history = session.getHistory();
|
||||
|
||||
reactor.dispatch(TLPT_TERM_RECEIVE_PARTIES, parties);
|
||||
},
|
||||
reactor.dispatch(TLPT_TERM_OPEN, {
|
||||
serverId,
|
||||
login,
|
||||
sid,
|
||||
isNewSession: true
|
||||
});
|
||||
|
||||
open(addr, login, sid){
|
||||
let isNew = !sid;
|
||||
if(isNew){
|
||||
sid = uuid();
|
||||
}
|
||||
|
||||
reactor.dispatch(TLPT_TERM_OPEN, {addr, login, sid, isNew} );
|
||||
history.push(routeUrl);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default actions;
|
||||
|
|
|
@ -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){
|
||||
return toImmutable({
|
||||
isConnecting: true,
|
||||
...settings
|
||||
function setActiveTerminal(state, {serverId, login, sid, isNewSession} ){
|
||||
return toImmutable({
|
||||
serverId,
|
||||
login,
|
||||
sid,
|
||||
isNewSession
|
||||
});
|
||||
}
|
||||
|
||||
function connected(state){
|
||||
return state.set('isConnected', true)
|
||||
.set('isConnecting', false);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
7
web/src/app/modules/app/actionTypes.js
Normal file
7
web/src/app/modules/app/actionTypes.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import keyMirror from 'keymirror'
|
||||
|
||||
export default keyMirror({
|
||||
TLPT_APP_INIT: null,
|
||||
TLPT_APP_FAILED: null,
|
||||
TLPT_APP_READY: null
|
||||
})
|
43
web/src/app/modules/app/actions.js
Normal file
43
web/src/app/modules/app/actions.js
Normal 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);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
}
|
22
web/src/app/modules/app/appStore.js
Normal file
22
web/src/app/modules/app/appStore.js
Normal 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));
|
||||
}
|
||||
})
|
5
web/src/app/modules/app/getters.js
Normal file
5
web/src/app/modules/app/getters.js
Normal file
|
@ -0,0 +1,5 @@
|
|||
const appState = [['tlpt'], app=> app.toJS()];
|
||||
|
||||
export default {
|
||||
appState
|
||||
}
|
3
web/src/app/modules/app/index.js
Normal file
3
web/src/app/modules/app/index.js
Normal file
|
@ -0,0 +1,3 @@
|
|||
module.exports.getters = require('./getters');
|
||||
module.exports.actions = require('./actions');
|
||||
module.exports.appStore = require('./appStore');
|
|
@ -1,9 +1,10 @@
|
|||
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'),
|
||||
'tlpt_invite': require('./invite/inviteStore'),
|
||||
'tlpt_rest_api': require('./restApi/restApiStore'),
|
||||
'tlpt_sessions': require('./sessions/sessionStore')
|
||||
'tlpt_sessions': require('./sessions/sessionStore')
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
|
@ -44,5 +46,5 @@ function getTags(node){
|
|||
|
||||
|
||||
export default {
|
||||
nodeListView
|
||||
nodeListView
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}).toJS();
|
||||
return sessions.valueSeq().map(createView).toJS();
|
||||
}];
|
||||
|
||||
const sessionViewById = (sid)=> [['tlpt_sessions', sid], (session)=>{
|
||||
if(!session){
|
||||
return null;
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -17,5 +17,5 @@ function updateSession(state, json){
|
|||
}
|
||||
|
||||
function receiveSessions(state, json){
|
||||
return state.merge(json);
|
||||
return toImmutable(json);
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ export default {
|
|||
|
||||
ensureUser(nextState, replace, cb){
|
||||
auth.ensureUser()
|
||||
.done((userData)=> {
|
||||
.done((userData)=> {
|
||||
reactor.dispatch(TLPT_RECEIVE_USER, userData.user );
|
||||
cb();
|
||||
})
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
@import "grv-sessions";
|
||||
@import "grv-nav";
|
||||
@import "grv-terminal-host";
|
||||
@import "grv-page-notfound";
|
||||
|
||||
.grv {
|
||||
background-color: white;
|
||||
|
|
|
@ -9,11 +9,10 @@
|
|||
|
||||
> li.active{
|
||||
border: none;
|
||||
background: #2f4050;
|
||||
background-color: #293846;
|
||||
}
|
||||
|
||||
li:hover{
|
||||
background-color: #2f4050;
|
||||
li:hover{
|
||||
}
|
||||
|
||||
li:first-child{
|
||||
|
|
14
web/src/styles/grv-page-notfound.scss
Normal file
14
web/src/styles/grv-page-notfound.scss
Normal 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;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue