Tightening security
This commit is contained in:
@@ -1 +1 @@
|
|||||||
99c6bbb0236daee684251a5f62bbd974
|
0eb34f66d9120fafc023eb90e9144592
|
||||||
@@ -280,7 +280,11 @@ class SecretPrompt {
|
|||||||
document.body.appendChild(overlay);
|
document.body.appendChild(overlay);
|
||||||
this._overlay = overlay;
|
this._overlay = overlay;
|
||||||
|
|
||||||
acceptBtn.focus();
|
requestAnimationFrame(() => {
|
||||||
|
overlay.classList.add('is-visible');
|
||||||
|
modal.classList.add('is-visible');
|
||||||
|
acceptBtn.focus();
|
||||||
|
});
|
||||||
|
|
||||||
acceptBtn.addEventListener('click', () => {
|
acceptBtn.addEventListener('click', () => {
|
||||||
this._close();
|
this._close();
|
||||||
|
|||||||
@@ -82,8 +82,6 @@ github.com/wailsapp/wails/v2 v2.12.0 h1:BHO/kLNWFHYjCzucxbzAYZWUjub1Tvb4cSguQozH
|
|||||||
github.com/wailsapp/wails/v2 v2.12.0/go.mod h1:mo1bzK1DEJrobt7YrBjgxvb5Sihb1mhAY09hppbibQg=
|
github.com/wailsapp/wails/v2 v2.12.0/go.mod h1:mo1bzK1DEJrobt7YrBjgxvb5Sihb1mhAY09hppbibQg=
|
||||||
github.com/zalando/go-keyring v0.2.8 h1:6sD/Ucpl7jNq10rM2pgqTs0sZ9V3qMrqfIIy5YPccHs=
|
github.com/zalando/go-keyring v0.2.8 h1:6sD/Ucpl7jNq10rM2pgqTs0sZ9V3qMrqfIIy5YPccHs=
|
||||||
github.com/zalando/go-keyring v0.2.8/go.mod h1:tsMo+VpRq5NGyKfxoBVjCuMrG47yj8cmakZDO5QGii0=
|
github.com/zalando/go-keyring v0.2.8/go.mod h1:tsMo+VpRq5NGyKfxoBVjCuMrG47yj8cmakZDO5QGii0=
|
||||||
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
|
||||||
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
|
||||||
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
golang.org/x/crypto v0.52.0 h1:RMs7fP2rXdep0CftQlK8Uf+kibLm7qkCcradZWYz988=
|
||||||
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
golang.org/x/crypto v0.52.0/go.mod h1:1QgfPxDqh0T2M/elOJtp9RvuR95kVjir0e6/BvEmGbc=
|
||||||
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
@@ -93,8 +91,6 @@ golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
|
||||||
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
|
||||||
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
golang.org/x/sys v0.45.0 h1:dO4czNzziLiiXplLQgBCEpCvXQ3dnkn0SdaZSYdQ+FY=
|
||||||
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
|||||||
@@ -57,14 +57,17 @@ type Session struct {
|
|||||||
// It coordinates between protocol clients, the storage layer,
|
// It coordinates between protocol clients, the storage layer,
|
||||||
// the secret store, and the Wails runtime for event emission.
|
// the secret store, and the Wails runtime for event emission.
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
sessions map[string]*Session
|
sessions map[string]*Session
|
||||||
store *storage.Store
|
store *storage.Store
|
||||||
secretStore *secrets.SecretStore
|
secretStore *secrets.SecretStore
|
||||||
keychainStore *secrets.KeychainStore
|
keychainStore *secrets.KeychainStore
|
||||||
auditLog *audit.Logger
|
auditLog *audit.Logger
|
||||||
recordStore *recording.Store
|
recordStore *recording.Store
|
||||||
ctx context.Context // Wails app context for event emission
|
ctx context.Context // Wails app context for event emission
|
||||||
|
hostKeyMu sync.Mutex
|
||||||
|
hostKeyChans map[string]chan bool
|
||||||
|
hostKeyListenerRegistered bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewManager creates a Manager wired to the given backing services.
|
// NewManager creates a Manager wired to the given backing services.
|
||||||
@@ -82,6 +85,7 @@ func NewManager(store *storage.Store, secretStore *secrets.SecretStore, keychain
|
|||||||
// SetContext stores the Wails runtime context for event emission.
|
// SetContext stores the Wails runtime context for event emission.
|
||||||
func (m *Manager) SetContext(ctx context.Context) {
|
func (m *Manager) SetContext(ctx context.Context) {
|
||||||
m.ctx = ctx
|
m.ctx = ctx
|
||||||
|
m.registerHostKeyResponseListener()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect creates a new session from a profile and connects it.
|
// Connect creates a new session from a profile and connects it.
|
||||||
@@ -1465,25 +1469,18 @@ func (m *Manager) promptHostKey(sessionID string, host string, keyType string, f
|
|||||||
|
|
||||||
requestID := uuid.New().String()
|
requestID := uuid.New().String()
|
||||||
ch := make(chan bool, 1)
|
ch := make(chan bool, 1)
|
||||||
cancel := wailsRuntime.EventsOn(m.ctx, "session:hostkey:response", func(optionalData ...interface{}) {
|
|
||||||
if len(optionalData) == 0 {
|
m.hostKeyMu.Lock()
|
||||||
return
|
if m.hostKeyChans == nil {
|
||||||
}
|
m.hostKeyChans = make(map[string]chan bool)
|
||||||
data, ok := optionalData[0].(map[string]interface{})
|
}
|
||||||
if !ok {
|
m.hostKeyChans[requestID] = ch
|
||||||
return
|
m.hostKeyMu.Unlock()
|
||||||
}
|
defer func() {
|
||||||
rid, _ := data["requestID"].(string)
|
m.hostKeyMu.Lock()
|
||||||
if rid != requestID {
|
delete(m.hostKeyChans, requestID)
|
||||||
return
|
m.hostKeyMu.Unlock()
|
||||||
}
|
}()
|
||||||
accepted, _ := data["accepted"].(bool)
|
|
||||||
select {
|
|
||||||
case ch <- accepted:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
})
|
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
m.emitEvent("session:hostkey:verify", map[string]interface{}{
|
m.emitEvent("session:hostkey:verify", map[string]interface{}{
|
||||||
"requestID": requestID,
|
"requestID": requestID,
|
||||||
@@ -1496,8 +1493,51 @@ func (m *Manager) promptHostKey(sessionID string, host string, keyType string, f
|
|||||||
select {
|
select {
|
||||||
case accepted := <-ch:
|
case accepted := <-ch:
|
||||||
return accepted, nil
|
return accepted, nil
|
||||||
case <-time.After(5 * time.Minute):
|
case <-time.After(60 * time.Second):
|
||||||
return false, fmt.Errorf("host key prompt timed out")
|
return false, fmt.Errorf("host key prompt timed out waiting for user response")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) registerHostKeyResponseListener() {
|
||||||
|
ctx := m.ctx
|
||||||
|
if ctx == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
m.hostKeyMu.Lock()
|
||||||
|
if m.hostKeyChans == nil {
|
||||||
|
m.hostKeyChans = make(map[string]chan bool)
|
||||||
|
}
|
||||||
|
if m.hostKeyListenerRegistered {
|
||||||
|
m.hostKeyMu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m.hostKeyListenerRegistered = true
|
||||||
|
m.hostKeyMu.Unlock()
|
||||||
|
|
||||||
|
wailsRuntime.EventsOn(ctx, "session:hostkey:response", m.handleHostKeyResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Manager) handleHostKeyResponse(optionalData ...interface{}) {
|
||||||
|
if len(optionalData) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data, ok := optionalData[0].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
requestID, _ := data["requestID"].(string)
|
||||||
|
accepted, _ := data["accepted"].(bool)
|
||||||
|
|
||||||
|
m.hostKeyMu.Lock()
|
||||||
|
ch, exists := m.hostKeyChans[requestID]
|
||||||
|
m.hostKeyMu.Unlock()
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
select {
|
||||||
|
case ch <- accepted:
|
||||||
|
default:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -189,6 +189,43 @@ func TestHostKeyCallbackStrictRejectsUnknownHost(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestHandleHostKeyResponseRoutesByRequestID(t *testing.T) {
|
||||||
|
mgr := NewManager(nil, nil, nil, nil, nil)
|
||||||
|
ch := make(chan bool, 1)
|
||||||
|
mgr.hostKeyChans = map[string]chan bool{"request-1": ch}
|
||||||
|
|
||||||
|
mgr.handleHostKeyResponse(map[string]interface{}{
|
||||||
|
"requestID": "request-1",
|
||||||
|
"accepted": true,
|
||||||
|
})
|
||||||
|
|
||||||
|
select {
|
||||||
|
case accepted := <-ch:
|
||||||
|
if !accepted {
|
||||||
|
t.Fatal("accepted = false, want true")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Fatal("expected host key response to be routed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleHostKeyResponseIgnoresOtherRequestIDs(t *testing.T) {
|
||||||
|
mgr := NewManager(nil, nil, nil, nil, nil)
|
||||||
|
ch := make(chan bool, 1)
|
||||||
|
mgr.hostKeyChans = map[string]chan bool{"request-1": ch}
|
||||||
|
|
||||||
|
mgr.handleHostKeyResponse(map[string]interface{}{
|
||||||
|
"requestID": "request-2",
|
||||||
|
"accepted": true,
|
||||||
|
})
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ch:
|
||||||
|
t.Fatal("unexpected host key response routed")
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func testPublicKey(t *testing.T) ssh.PublicKey {
|
func testPublicKey(t *testing.T) ssh.PublicKey {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
pub, _, err := ed25519.GenerateKey(rand.Reader)
|
pub, _, err := ed25519.GenerateKey(rand.Reader)
|
||||||
|
|||||||
@@ -167,10 +167,9 @@ func (c *Client) connectInternal(
|
|||||||
|
|
||||||
sessionEnv := terminalenv.SSHEnvVars(sc.EnvVars)
|
sessionEnv := terminalenv.SSHEnvVars(sc.EnvVars)
|
||||||
for _, key := range terminalenv.SortedEnvKeys(sessionEnv) {
|
for _, key := range terminalenv.SortedEnvKeys(sessionEnv) {
|
||||||
// Servers may reject env requests unless AcceptEnv permits them. Locale
|
// Some SSH servers never reply to env requests. Send them without
|
||||||
// requests improve Unicode input when accepted, but rejection should not
|
// requiring a reply so optional locale/profile env cannot stall login.
|
||||||
// prevent the terminal session from starting.
|
sendSessionEnv(session, key, sessionEnv[key])
|
||||||
_ = session.Setenv(key, sessionEnv[key])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := session.RequestPty(termType, rows, cols, modes); err != nil {
|
if err := session.RequestPty(termType, rows, cols, modes); err != nil {
|
||||||
@@ -244,6 +243,16 @@ func (c *Client) connectInternal(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sendSessionEnv(session *ssh.Session, name, value string) {
|
||||||
|
_, _ = session.SendRequest("env", false, ssh.Marshal(struct {
|
||||||
|
Name string
|
||||||
|
Value string
|
||||||
|
}{
|
||||||
|
Name: name,
|
||||||
|
Value: value,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
// Write sends data to the SSH session's stdin.
|
// Write sends data to the SSH session's stdin.
|
||||||
func (c *Client) Write(data []byte) error {
|
func (c *Client) Write(data []byte) error {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
|
|||||||
Reference in New Issue
Block a user