mirror of
https://github.com/gravitational/teleport.git
synced 2026-09-21 14:35:22 +08:00
Added tsh for Windows.
This commit is contained in:
committed by
Russell Jones
parent
7cfc78bafc
commit
87fe7a397d
+21
-1
@@ -211,9 +211,15 @@ const (
|
||||
// LinuxAdminGID is the ID of the standard adm group on linux
|
||||
LinuxAdminGID = 4
|
||||
|
||||
// LinuxOS is the name of the linux OS
|
||||
// LinuxOS is the GOOS constant used for Linux.
|
||||
LinuxOS = "linux"
|
||||
|
||||
// WindowsOS is the GOOS constant used for Microsoft Windows.
|
||||
WindowsOS = "windows"
|
||||
|
||||
// DarwinOS is the GOOS constant for Apple macOS/darwin.
|
||||
DarwinOS = "darwin"
|
||||
|
||||
// DirMaskSharedGroup is the mask for a directory accessible
|
||||
// by the owner and group
|
||||
DirMaskSharedGroup = 0770
|
||||
@@ -377,6 +383,9 @@ const (
|
||||
// EnvHome is home environment variable
|
||||
EnvHome = "HOME"
|
||||
|
||||
// EnvUserProfile is the home directory environment variable on Windows.
|
||||
EnvUserProfile = "USERPROFILE"
|
||||
|
||||
// KubeServiceAddr is an address for kubernetes endpoint service
|
||||
KubeServiceAddr = "kubernetes.default.svc.cluster.local:443"
|
||||
|
||||
@@ -411,3 +420,14 @@ const (
|
||||
// go standard lib are using that is the only way to identify some errors
|
||||
UseOfClosedNetworkConnection = "use of closed network connection"
|
||||
)
|
||||
|
||||
const (
|
||||
// OpenBrowserLinux is the command used to open a web browser on Linux.
|
||||
OpenBrowserLinux = "sensible-browser"
|
||||
|
||||
// OpenBrowserDarwin is the command used to open a web browser on macOS/Darwin.
|
||||
OpenBrowserDarwin = "open"
|
||||
|
||||
// OpenBrowserWindows is the command used to open a web browser on Windows.
|
||||
OpenBrowserWindows = "rundll32.exe"
|
||||
)
|
||||
|
||||
+7
-8
@@ -33,6 +33,7 @@ import (
|
||||
"os/user"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@@ -665,7 +666,10 @@ func NewClient(c *Config) (tc *TeleportClient, err error) {
|
||||
|
||||
// accessPoint returns access point based on the cache policy
|
||||
func (tc *TeleportClient) accessPoint(clt auth.AccessPoint, proxyHostPort string, clusterName string) (auth.AccessPoint, error) {
|
||||
if tc.CachePolicy == nil {
|
||||
// If no caching policy was set or on Windows (where Teleport does not
|
||||
// support file locking at the moment), return direct access to the access
|
||||
// point.
|
||||
if tc.CachePolicy == nil || runtime.GOOS == teleport.WindowsOS {
|
||||
log.Debugf("not using caching access point")
|
||||
return clt, nil
|
||||
}
|
||||
@@ -1681,15 +1685,10 @@ func loopbackPool(proxyAddr string) *x509.CertPool {
|
||||
return certPool
|
||||
}
|
||||
|
||||
// connects to a local SSH agent
|
||||
// connectToSSHAgent connects to the local SSH agent and returns a agent.Agent.
|
||||
func connectToSSHAgent() agent.Agent {
|
||||
socketPath := os.Getenv(teleport.SSHAuthSock)
|
||||
if socketPath == "" {
|
||||
log.Infof("[KEY AGENT] %v is not set. Try running eval `ssh-agent` and trying again.", teleport.SSHAuthSock)
|
||||
return nil
|
||||
}
|
||||
|
||||
conn, err := agentconn.DialAgent(socketPath)
|
||||
conn, err := agentconn.Dial(socketPath)
|
||||
if err != nil {
|
||||
log.Errorf("[KEY AGENT] Unable to connect to SSH agent on socket: %q.", socketPath)
|
||||
return nil
|
||||
|
||||
+28
-11
@@ -19,11 +19,14 @@ package client
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/gravitational/teleport"
|
||||
"github.com/gravitational/teleport/lib/tlsca"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
)
|
||||
@@ -46,16 +49,6 @@ type Key struct {
|
||||
|
||||
// AsAgentKeys converts client.Key struct to a []*agent.AddedKey. All elements
|
||||
// of the []*agent.AddedKey slice need to be loaded into the agent!
|
||||
//
|
||||
// This is done because OpenSSH clients older than OpenSSH 7.3/7.3p1
|
||||
// (2016-08-01) have a bug in how they use certificates that have been loaded
|
||||
// in an agent. Specifically when you add a certificate to an agent, you can't
|
||||
// just embed the private key within the certificate, you have to add the
|
||||
// certificate and private key to the agent separately. Teleport works around
|
||||
// this behavior to ensure OpenSSH interoperability.
|
||||
//
|
||||
// For more details see the following: https://bugzilla.mindrot.org/show_bug.cgi?id=2550
|
||||
// WARNING: callers expect the returned slice to be __exactly as it is__
|
||||
func (k *Key) AsAgentKeys() ([]*agent.AddedKey, error) {
|
||||
// unmarshal certificate bytes into a ssh.PublicKey
|
||||
publicKey, _, _, _, err := ssh.ParseAuthorizedKey(k.Cert)
|
||||
@@ -72,7 +65,31 @@ func (k *Key) AsAgentKeys() ([]*agent.AddedKey, error) {
|
||||
// put a teleport identifier along with the teleport user into the comment field
|
||||
comment := fmt.Sprintf("teleport:%v", publicKey.(*ssh.Certificate).KeyId)
|
||||
|
||||
// return a certificate (with embedded private key) as well as a private key
|
||||
// On Windows, return the certificate with the private key embedded.
|
||||
if runtime.GOOS == teleport.WindowsOS {
|
||||
return []*agent.AddedKey{
|
||||
&agent.AddedKey{
|
||||
PrivateKey: privateKey,
|
||||
Certificate: publicKey.(*ssh.Certificate),
|
||||
Comment: comment,
|
||||
LifetimeSecs: 0,
|
||||
ConfirmBeforeUse: false,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// On Unix, return the certificate (with embedded private key) as well as
|
||||
// a private key.
|
||||
//
|
||||
// This is done because OpenSSH clients older than OpenSSH 7.3/7.3p1
|
||||
// (2016-08-01) have a bug in how they use certificates that have been loaded
|
||||
// in an agent. Specifically when you add a certificate to an agent, you can't
|
||||
// just embed the private key within the certificate, you have to add the
|
||||
// certificate and private key to the agent separately. Teleport works around
|
||||
// this behavior to ensure OpenSSH interoperability.
|
||||
//
|
||||
// For more details see the following: https://bugzilla.mindrot.org/show_bug.cgi?id=2550
|
||||
// WARNING: callers expect the returned slice to be __exactly as it is__
|
||||
return []*agent.AddedKey{
|
||||
&agent.AddedKey{
|
||||
PrivateKey: privateKey,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// +build !windows
|
||||
|
||||
/*
|
||||
Copyright 2016 Gravitational, Inc.
|
||||
|
||||
@@ -22,13 +24,18 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"golang.org/x/crypto/ssh/agent"
|
||||
|
||||
"github.com/docker/docker/pkg/term"
|
||||
"github.com/gravitational/teleport"
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
"github.com/gravitational/teleport/lib/events"
|
||||
"github.com/gravitational/teleport/lib/session"
|
||||
"github.com/gravitational/teleport/lib/sshutils"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
@@ -277,6 +284,109 @@ func (ns *NodeSession) allocateTerminal(termType string, s *ssh.Session) (io.Rea
|
||||
), nil
|
||||
}
|
||||
|
||||
func (ns *NodeSession) updateTerminalSize(s *ssh.Session) {
|
||||
// SIGWINCH is sent to the process when the window size of the terminal has
|
||||
// changed.
|
||||
sigwinchCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigwinchCh, syscall.SIGWINCH)
|
||||
|
||||
lastSize, err := term.GetWinsize(0)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to get window size: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Sync the local terminal with size received from the remote server every
|
||||
// two seconds. If we try and do it live, synchronization jitters occur.
|
||||
tickerCh := time.NewTicker(defaults.TerminalResizePeriod)
|
||||
defer tickerCh.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
// The client updated the size of the local PTY. This change needs to occur
|
||||
// on the server side PTY as well.
|
||||
case sigwinch := <-sigwinchCh:
|
||||
if sigwinch == nil {
|
||||
return
|
||||
}
|
||||
|
||||
currSize, err := term.GetWinsize(0)
|
||||
if err != nil {
|
||||
log.Warnf("Unable to get window size: %v.", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Terminal size has not changed, don't do anything.
|
||||
if currSize.Height == lastSize.Height && currSize.Width == lastSize.Width {
|
||||
continue
|
||||
}
|
||||
|
||||
// Send the "window-change" request over the channel.
|
||||
_, err = s.SendRequest(
|
||||
sshutils.WindowChangeRequest,
|
||||
false,
|
||||
ssh.Marshal(sshutils.WinChangeReqParams{
|
||||
W: uint32(currSize.Width),
|
||||
H: uint32(currSize.Height),
|
||||
}))
|
||||
if err != nil {
|
||||
log.Warnf("Unable to send %v reqest: %v.", sshutils.WindowChangeRequest, err)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Debugf("Updated window size from %v to %v due to SIGWINCH.", lastSize, currSize)
|
||||
|
||||
lastSize = currSize
|
||||
|
||||
// Extract "resize" events in the stream and store the last window size.
|
||||
case event := <-ns.nodeClient.TC.EventsChannel():
|
||||
// Only "resize" events are important to tsh, all others can be ignored.
|
||||
if event.GetType() != events.ResizeEvent {
|
||||
continue
|
||||
}
|
||||
|
||||
terminalParams, err := session.UnmarshalTerminalParams(event.GetString(events.TerminalSize))
|
||||
if err != nil {
|
||||
log.Warnf("Unable to unmarshal terminal parameters: %v.", err)
|
||||
continue
|
||||
}
|
||||
|
||||
lastSize = terminalParams.Winsize()
|
||||
log.Debugf("Recevied window size %v from node in session.\n", lastSize, event.GetString(events.SessionEventID))
|
||||
|
||||
// Update size of local terminal with the last size received from remote server.
|
||||
case <-tickerCh.C:
|
||||
// Get the current size of the terminal and the last size report that was
|
||||
// received.
|
||||
currSize, err := term.GetWinsize(0)
|
||||
if err != nil {
|
||||
log.Warnf("Unable to get current terminal size: %v.", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Terminal size has not changed, don't do anything.
|
||||
if currSize.Width == lastSize.Width && currSize.Height == lastSize.Height {
|
||||
continue
|
||||
}
|
||||
|
||||
// This changes the size of the local PTY. This will re-draw what's within
|
||||
// the window.
|
||||
err = term.SetWinsize(0, lastSize)
|
||||
if err != nil {
|
||||
log.Warnf("Unable to update terminal size: %v.\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// This is what we use to resize the physical terminal window itself.
|
||||
os.Stdout.Write([]byte(fmt.Sprintf("\x1b[8;%d;%dt", lastSize.Height, lastSize.Width)))
|
||||
|
||||
log.Debugf("Updated window size from to %v due to remote window change.", currSize, lastSize)
|
||||
case <-ns.closer.C:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// isTerminalAttached returns true when this session is be controlled by
|
||||
// a real terminal.
|
||||
// It will return False for sessions initiated by the Web client or
|
||||
@@ -375,6 +485,42 @@ func (ns *NodeSession) runCommand(ctx context.Context, cmd []string, callback Sh
|
||||
})
|
||||
}
|
||||
|
||||
// watchSignals register UNIX signal handlers and properly terminates a remote shell session
|
||||
// must be called as a goroutine right after a remote shell is created
|
||||
func (ns *NodeSession) watchSignals(shell io.Writer) {
|
||||
exitSignals := make(chan os.Signal, 1)
|
||||
// catch SIGTERM
|
||||
signal.Notify(exitSignals, syscall.SIGTERM)
|
||||
go func() {
|
||||
defer ns.closer.Close()
|
||||
<-exitSignals
|
||||
}()
|
||||
// Catch Ctrl-C signal
|
||||
ctrlCSignal := make(chan os.Signal, 1)
|
||||
signal.Notify(ctrlCSignal, syscall.SIGINT)
|
||||
go func() {
|
||||
for {
|
||||
<-ctrlCSignal
|
||||
_, err := shell.Write([]byte{3})
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
}
|
||||
}
|
||||
}()
|
||||
// Catch Ctrl-Z signal
|
||||
ctrlZSignal := make(chan os.Signal, 1)
|
||||
signal.Notify(ctrlZSignal, syscall.SIGTSTP)
|
||||
go func() {
|
||||
for {
|
||||
<-ctrlZSignal
|
||||
_, err := shell.Write([]byte{26})
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// pipeInOut launches two goroutines: one to pipe the local input into the remote shell,
|
||||
// and another to pipe the output of the remote shell into the local output
|
||||
func (ns *NodeSession) pipeInOut(shell io.ReadWriteCloser) {
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
"github.com/gravitational/teleport/lib/events"
|
||||
"github.com/gravitational/teleport/lib/session"
|
||||
"github.com/gravitational/teleport/lib/sshutils"
|
||||
"github.com/moby/moby/pkg/term"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (ns *NodeSession) updateTerminalSize(s *ssh.Session) {
|
||||
// SIGWINCH is sent to the process when the window size of the terminal has
|
||||
// changed.
|
||||
sigwinchCh := make(chan os.Signal, 1)
|
||||
signal.Notify(sigwinchCh, syscall.SIGWINCH)
|
||||
|
||||
lastSize, err := term.GetWinsize(0)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to get window size: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Sync the local terminal with size received from the remote server every
|
||||
// two seconds. If we try and do it live, synchronization jitters occur.
|
||||
tickerCh := time.NewTicker(defaults.TerminalResizePeriod)
|
||||
defer tickerCh.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
// The client updated the size of the local PTY. This change needs to occur
|
||||
// on the server side PTY as well.
|
||||
case sigwinch := <-sigwinchCh:
|
||||
if sigwinch == nil {
|
||||
return
|
||||
}
|
||||
|
||||
currSize, err := term.GetWinsize(0)
|
||||
if err != nil {
|
||||
log.Warnf("Unable to get window size: %v.", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Terminal size has not changed, don't do anything.
|
||||
if currSize.Height == lastSize.Height && currSize.Width == lastSize.Width {
|
||||
continue
|
||||
}
|
||||
|
||||
// Send the "window-change" request over the channel.
|
||||
_, err = s.SendRequest(
|
||||
sshutils.WindowChangeRequest,
|
||||
false,
|
||||
ssh.Marshal(sshutils.WinChangeReqParams{
|
||||
W: uint32(currSize.Width),
|
||||
H: uint32(currSize.Height),
|
||||
}))
|
||||
if err != nil {
|
||||
log.Warnf("Unable to send %v reqest: %v.", sshutils.WindowChangeRequest, err)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Debugf("Updated window size from %v to %v due to SIGWINCH.", lastSize, currSize)
|
||||
|
||||
lastSize = currSize
|
||||
|
||||
// Extract "resize" events in the stream and store the last window size.
|
||||
case event := <-ns.nodeClient.TC.EventsChannel():
|
||||
// Only "resize" events are important to tsh, all others can be ignored.
|
||||
if event.GetType() != events.ResizeEvent {
|
||||
continue
|
||||
}
|
||||
|
||||
terminalParams, err := session.UnmarshalTerminalParams(event.GetString(events.TerminalSize))
|
||||
if err != nil {
|
||||
log.Warnf("Unable to unmarshal terminal parameters: %v.", err)
|
||||
continue
|
||||
}
|
||||
|
||||
lastSize = terminalParams.Winsize()
|
||||
log.Debugf("Recevied window size %v from node in session.\n", lastSize, event.GetString(events.SessionEventID))
|
||||
|
||||
// Update size of local terminal with the last size received from remote server.
|
||||
case <-tickerCh.C:
|
||||
// Get the current size of the terminal and the last size report that was
|
||||
// received.
|
||||
currSize, err := term.GetWinsize(0)
|
||||
if err != nil {
|
||||
log.Warnf("Unable to get current terminal size: %v.", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Terminal size has not changed, don't do anything.
|
||||
if currSize.Width == lastSize.Width && currSize.Height == lastSize.Height {
|
||||
continue
|
||||
}
|
||||
|
||||
// This changes the size of the local PTY. This will re-draw what's within
|
||||
// the window.
|
||||
err = term.SetWinsize(0, lastSize)
|
||||
if err != nil {
|
||||
log.Warnf("Unable to update terminal size: %v.\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// This is what we use to resize the physical terminal window itself.
|
||||
os.Stdout.Write([]byte(fmt.Sprintf("\x1b[8;%d;%dt", lastSize.Height, lastSize.Width)))
|
||||
|
||||
log.Debugf("Updated window size from to %v due to remote window change.", currSize, lastSize)
|
||||
case <-ns.closer.C:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// watchSignals register UNIX signal handlers and properly terminates a remote shell session
|
||||
// must be called as a goroutine right after a remote shell is created
|
||||
func (ns *NodeSession) watchSignals(shell io.Writer) {
|
||||
exitSignals := make(chan os.Signal, 1)
|
||||
// catch SIGTERM
|
||||
signal.Notify(exitSignals, syscall.SIGTERM)
|
||||
go func() {
|
||||
defer ns.closer.Close()
|
||||
<-exitSignals
|
||||
}()
|
||||
// Catch Ctrl-C signal
|
||||
ctrlCSignal := make(chan os.Signal, 1)
|
||||
signal.Notify(ctrlCSignal, syscall.SIGINT)
|
||||
go func() {
|
||||
for {
|
||||
<-ctrlCSignal
|
||||
_, err := shell.Write([]byte{3})
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
}
|
||||
}
|
||||
}()
|
||||
// Catch Ctrl-Z signal
|
||||
ctrlZSignal := make(chan os.Signal, 1)
|
||||
signal.Notify(ctrlZSignal, syscall.SIGTSTP)
|
||||
go func() {
|
||||
for {
|
||||
<-ctrlZSignal
|
||||
_, err := shell.Write([]byte{26})
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
@@ -1,40 +1,62 @@
|
||||
// +build windows
|
||||
|
||||
/*
|
||||
Copyright 2018 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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 client
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"context"
|
||||
"io"
|
||||
|
||||
"golang.org/x/crypto/ssh"
|
||||
"github.com/gravitational/teleport/lib/session"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/gravitational/trace"
|
||||
)
|
||||
|
||||
func (ns *NodeSession) updateTerminalSize(s *ssh.Session) {
|
||||
return
|
||||
// NodeSession is a bare minimum implementation to get Windows to compile.
|
||||
// This sits behind a build flag because github.com/docker/docker/pkg/term
|
||||
// on Windows does not support "SetWinsize". Because tsh on Windows does not
|
||||
// support "tsh ssh" this code will never be called.
|
||||
type NodeSession struct {
|
||||
ExitMsg string
|
||||
}
|
||||
|
||||
// watchSignals register UNIX signal handlers and properly terminates a remote shell session
|
||||
// must be called as a goroutine right after a remote shell is created
|
||||
func (ns *NodeSession) watchSignals(shell io.Writer) {
|
||||
exitSignals := make(chan os.Signal, 1)
|
||||
// catch SIGTERM
|
||||
signal.Notify(exitSignals, syscall.SIGTERM)
|
||||
go func() {
|
||||
defer ns.closer.Close()
|
||||
<-exitSignals
|
||||
}()
|
||||
// Catch Ctrl-C signal
|
||||
ctrlCSignal := make(chan os.Signal, 1)
|
||||
signal.Notify(ctrlCSignal, syscall.SIGINT)
|
||||
go func() {
|
||||
for {
|
||||
<-ctrlCSignal
|
||||
_, err := shell.Write([]byte{3})
|
||||
if err != nil {
|
||||
log.Errorf(err.Error())
|
||||
}
|
||||
}
|
||||
}()
|
||||
func newSession(client *NodeClient,
|
||||
joinSession *session.Session,
|
||||
env map[string]string,
|
||||
stdin io.Reader,
|
||||
stdout io.Writer,
|
||||
stderr io.Writer) (*NodeSession, error) {
|
||||
|
||||
return nil, trace.BadParameter("sessions not supported on Windows")
|
||||
}
|
||||
|
||||
func (ns *NodeSession) runCommand(ctx context.Context, cmd []string, callback ShellCreatedCallback, interactive bool) error {
|
||||
return trace.BadParameter("sessions not supported on Windows")
|
||||
}
|
||||
|
||||
func (ns *NodeSession) runShell(callback ShellCreatedCallback) error {
|
||||
return trace.BadParameter("sessions not supported on Windows")
|
||||
}
|
||||
|
||||
func (ns *NodeSession) NodeClient() *NodeClient {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *NodeSession) Close() error {
|
||||
return trace.BadParameter("sessions not supported on Windows")
|
||||
}
|
||||
|
||||
+23
-32
@@ -12,7 +12,6 @@ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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 client
|
||||
@@ -27,20 +26,19 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gravitational/teleport"
|
||||
"github.com/gravitational/teleport/lib/auth"
|
||||
|
||||
"github.com/gravitational/roundtrip"
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
"github.com/mailgun/lemma/secret"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/tstranex/u2f"
|
||||
)
|
||||
|
||||
@@ -156,20 +154,8 @@ func SSHAgentSSOLogin(ctx context.Context, proxyAddr, connectorID string, pubKey
|
||||
proxyURL.Path = "/web/msg/info/login_success"
|
||||
redirectSuccessURL := proxyURL.String()
|
||||
|
||||
var ssoLoginURL string
|
||||
|
||||
makeHandler := func(fn func(http.ResponseWriter, *http.Request) (*auth.SSHLoginResponse, error)) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path == "/login" {
|
||||
if ssoLoginURL == "" {
|
||||
http.Error(w, "SSO Login URL is undefined", http.StatusInternalServerError)
|
||||
} else {
|
||||
http.Redirect(w, r, ssoLoginURL, http.StatusFound)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
response, err := fn(w, r)
|
||||
if err != nil {
|
||||
if trace.IsNotFound(err) {
|
||||
@@ -239,32 +225,37 @@ func SSHAgentSSOLogin(ctx context.Context, proxyAddr, connectorID string, pubKey
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
ssoLoginURL = re.RedirectURL
|
||||
|
||||
// If a command was found to launch the browser, create and start it.
|
||||
var execCmd *exec.Cmd
|
||||
if runtime.GOOS == "windows" {
|
||||
loginRedirectURL := server.URL + "/login"
|
||||
fmt.Printf("If browser window does not open automatically, open it by clicking on the link:\n %v\n", loginRedirectURL)
|
||||
execCmd = exec.Command(filepath.Join(os.Getenv("SYSTEMROOT"), "System32", "rundll32.exe"), "url.dll,FileProtocolHandler", loginRedirectURL)
|
||||
} else {
|
||||
fmt.Printf("If browser window does not open automatically, open it by clicking on the link:\n %v\n", re.RedirectURL)
|
||||
|
||||
var command = "sensible-browser"
|
||||
if runtime.GOOS == "darwin" {
|
||||
command = "open"
|
||||
switch runtime.GOOS {
|
||||
// macOS.
|
||||
case teleport.DarwinOS:
|
||||
path, err := exec.LookPath(teleport.OpenBrowserDarwin)
|
||||
if err == nil {
|
||||
execCmd = exec.Command(path, re.RedirectURL)
|
||||
}
|
||||
|
||||
path, err := exec.LookPath(command)
|
||||
// Windows.
|
||||
case teleport.WindowsOS:
|
||||
path, err := exec.LookPath(teleport.OpenBrowserWindows)
|
||||
if err == nil {
|
||||
execCmd = exec.Command(path, "url.dll,FileProtocolHandler", re.RedirectURL)
|
||||
}
|
||||
// Linux or any other operating sytem.
|
||||
default:
|
||||
path, err := exec.LookPath(teleport.OpenBrowserLinux)
|
||||
if err == nil {
|
||||
execCmd = exec.Command(path, re.RedirectURL)
|
||||
}
|
||||
}
|
||||
|
||||
if execCmd != nil {
|
||||
execCmd.Start()
|
||||
}
|
||||
|
||||
log.Infof("waiting for response on %v", server.URL)
|
||||
// Print to screen in-case the command that launches the browser did not run.
|
||||
fmt.Printf("If browser window does not open automatically, open it by ")
|
||||
fmt.Printf("clicking on the link:\n %v\n", re.RedirectURL)
|
||||
|
||||
log.Infof("Waiting for response at: %v.", server.URL)
|
||||
|
||||
select {
|
||||
case err := <-errorC:
|
||||
|
||||
@@ -439,3 +439,7 @@ const (
|
||||
HMACSHA1 = "hmac-sha1"
|
||||
HMACSHA196 = "hmac-sha1-96"
|
||||
)
|
||||
|
||||
// WindowsOpenSSHNamedPipe is the address of the named pipe that the
|
||||
// OpenSSH agent is on.
|
||||
const WindowsOpenSSHNamedPipe = `\\.\pipe\openssh-ssh-agent`
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
Copyright 2018 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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 events
|
||||
|
||||
import "gopkg.in/check.v1"
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
Copyright 2018 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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 events
|
||||
|
||||
import (
|
||||
|
||||
@@ -1,3 +1,19 @@
|
||||
/*
|
||||
Copyright 2018 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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 events
|
||||
|
||||
import (
|
||||
|
||||
@@ -17,6 +17,7 @@ limitations under the License.
|
||||
package events
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
@@ -136,6 +137,25 @@ func (sl *DiskSessionLogger) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func openFileForTar(filename string) (*tar.Header, io.ReadCloser, error) {
|
||||
fi, err := os.Stat(filename)
|
||||
if err != nil {
|
||||
return nil, nil, trace.ConvertSystemError(err)
|
||||
}
|
||||
|
||||
header, err := tar.FileInfoHeader(fi, "")
|
||||
if err != nil {
|
||||
return nil, nil, trace.ConvertSystemError(err)
|
||||
}
|
||||
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, nil, trace.ConvertSystemError(err)
|
||||
}
|
||||
|
||||
return header, f, nil
|
||||
}
|
||||
|
||||
// Finalize is called by the session when it's closing. This is where we're
|
||||
// releasing audit resources associated with the session
|
||||
func (sl *DiskSessionLogger) Finalize() error {
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package events
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
)
|
||||
|
||||
func openFileForTar(filename string) (*tar.Header, io.ReadCloser, error) {
|
||||
fi, err := os.Stat(filename)
|
||||
if err != nil {
|
||||
return nil, nil, trace.ConvertSystemError(err)
|
||||
}
|
||||
header := tar.Header{
|
||||
Name: filepath.Base(filename),
|
||||
Size: fi.Size(),
|
||||
Mode: int64(fi.Mode()),
|
||||
ModTime: fi.ModTime(),
|
||||
}
|
||||
sys, ok := fi.Sys().(*syscall.Stat_t)
|
||||
if ok {
|
||||
header.Uid = int(sys.Uid)
|
||||
header.Gid = int(sys.Gid)
|
||||
}
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, nil, trace.ConvertSystemError(err)
|
||||
}
|
||||
return &header, f, nil
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package events
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
)
|
||||
|
||||
func openFileForTar(filename string) (*tar.Header, io.ReadCloser, error) {
|
||||
fi, err := os.Stat(filename)
|
||||
if err != nil {
|
||||
return nil, nil, trace.ConvertSystemError(err)
|
||||
}
|
||||
header := tar.Header{
|
||||
Name: filepath.Base(filename),
|
||||
Size: fi.Size(),
|
||||
Mode: int64(fi.Mode()),
|
||||
ModTime: fi.ModTime(),
|
||||
}
|
||||
f, err := os.Open(filename)
|
||||
if err != nil {
|
||||
return nil, nil, trace.ConvertSystemError(err)
|
||||
}
|
||||
return &header, f, nil
|
||||
}
|
||||
+12
-55
@@ -16,72 +16,29 @@ limitations under the License.
|
||||
|
||||
package shell
|
||||
|
||||
/*
|
||||
#cgo solaris CFLAGS: -D_POSIX_PTHREAD_SEMANTICS
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <pwd.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static int mygetpwnam_r(const char *name, struct passwd *pwd,
|
||||
char *buf, size_t buflen, struct passwd **result) {
|
||||
return getpwnam_r(name, pwd, buf, buflen, result);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"os/user"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultShell = "/bin/sh"
|
||||
)
|
||||
|
||||
// GetLoginShell determines the login shell for a given username
|
||||
// GetLoginShell determines the login shell for a given username.
|
||||
func GetLoginShell(username string) (string, error) {
|
||||
// see if the username is valid
|
||||
_, err := user.Lookup(username)
|
||||
var err error
|
||||
var shellcmd string
|
||||
|
||||
shellcmd, err = getLoginShell(username)
|
||||
if err != nil {
|
||||
if !trace.IsNotFound(err) {
|
||||
logrus.Warnf("No shell specified for %v, using default %v.", username, DefaultShell)
|
||||
return DefaultShell, nil
|
||||
}
|
||||
return "", trace.Wrap(err)
|
||||
}
|
||||
|
||||
// based on stdlib user/lookup_unix.go packages which does not return user shell
|
||||
// https://golang.org/src/os/user/lookup_unix.go
|
||||
var pwd C.struct_passwd
|
||||
var result *C.struct_passwd
|
||||
|
||||
bufSize := C.sysconf(C._SC_GETPW_R_SIZE_MAX)
|
||||
if bufSize == -1 {
|
||||
bufSize = 1024
|
||||
}
|
||||
if bufSize <= 0 || bufSize > 1<<20 {
|
||||
return "", trace.Errorf("lookupPosixShell: unreasonable _SC_GETPW_R_SIZE_MAX of %d", bufSize)
|
||||
}
|
||||
buf := C.malloc(C.size_t(bufSize))
|
||||
defer C.free(buf)
|
||||
var rv C.int
|
||||
nameC := C.CString(username)
|
||||
defer C.free(unsafe.Pointer(nameC))
|
||||
rv = C.mygetpwnam_r(nameC,
|
||||
&pwd,
|
||||
(*C.char)(buf),
|
||||
C.size_t(bufSize),
|
||||
&result)
|
||||
if rv != 0 || result == nil {
|
||||
log.Errorf("lookupPosixShell: lookup username %s: %s", username, syscall.Errno(rv))
|
||||
return "", trace.Errorf("cannot determine shell for %s", username)
|
||||
}
|
||||
shellCmd := strings.TrimSpace(C.GoString(pwd.pw_shell))
|
||||
if len(shellCmd) == 0 {
|
||||
log.Warnf("no shell specified for %s. using default=%s", username, DefaultShell)
|
||||
shellCmd = DefaultShell
|
||||
}
|
||||
return shellCmd, nil
|
||||
return shellcmd, nil
|
||||
}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
// +build !cgo
|
||||
|
||||
package shell
|
||||
|
||||
const (
|
||||
DefaultShell = "/bin/sh"
|
||||
)
|
||||
|
||||
func GetLoginShell(username string) (string, error) {
|
||||
return DefaultShell, nil
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
// +build !windows
|
||||
|
||||
/*
|
||||
Copyright 2017 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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 shell
|
||||
|
||||
/*
|
||||
#cgo solaris CFLAGS: -D_POSIX_PTHREAD_SEMANTICS
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <pwd.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
static int mygetpwnam_r(const char *name, struct passwd *pwd,
|
||||
char *buf, size_t buflen, struct passwd **result) {
|
||||
return getpwnam_r(name, pwd, buf, buflen, result);
|
||||
}
|
||||
*/
|
||||
import "C"
|
||||
|
||||
import (
|
||||
"os/user"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// getLoginShell determines the login shell for a given username
|
||||
func getLoginShell(username string) (string, error) {
|
||||
// See if the username is valid.
|
||||
_, err := user.Lookup(username)
|
||||
if err != nil {
|
||||
return "", trace.Wrap(err)
|
||||
}
|
||||
|
||||
// Based on stdlib user/lookup_unix.go packages which does not return
|
||||
// user shell: https://golang.org/src/os/user/lookup_unix.go
|
||||
var pwd C.struct_passwd
|
||||
var result *C.struct_passwd
|
||||
|
||||
bufSize := C.sysconf(C._SC_GETPW_R_SIZE_MAX)
|
||||
if bufSize == -1 {
|
||||
bufSize = 1024
|
||||
}
|
||||
if bufSize <= 0 || bufSize > 1<<20 {
|
||||
return "", trace.BadParameter("lookupPosixShell: unreasonable _SC_GETPW_R_SIZE_MAX of %d", bufSize)
|
||||
}
|
||||
buf := C.malloc(C.size_t(bufSize))
|
||||
defer C.free(buf)
|
||||
var rv C.int
|
||||
nameC := C.CString(username)
|
||||
defer C.free(unsafe.Pointer(nameC))
|
||||
rv = C.mygetpwnam_r(nameC,
|
||||
&pwd,
|
||||
(*C.char)(buf),
|
||||
C.size_t(bufSize),
|
||||
&result)
|
||||
if rv != 0 || result == nil {
|
||||
log.Errorf("lookupPosixShell: lookup username %s: %s", username, syscall.Errno(rv))
|
||||
return "", trace.BadParameter("cannot determine shell for %s", username)
|
||||
}
|
||||
|
||||
// If no shell was found, return trace.NotFound to allow the caller to set
|
||||
// the default shell.
|
||||
shellCmd := strings.TrimSpace(C.GoString(pwd.pw_shell))
|
||||
if len(shellCmd) == 0 {
|
||||
return "", trace.NotFound("no shell specified for %v", username)
|
||||
}
|
||||
|
||||
return shellCmd, nil
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// +build windows
|
||||
|
||||
/*
|
||||
Copyright 2018 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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 shell
|
||||
|
||||
import (
|
||||
"github.com/gravitational/trace"
|
||||
)
|
||||
|
||||
// getLoginShell always return an error on Windows. This code his behind a
|
||||
// build flag to allow cross compilation (Unix version uses CGO).
|
||||
func getLoginShell(username string) (string, error) {
|
||||
return "", trace.BadParameter("login shell on Windows is not supported")
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// +build !windows
|
||||
|
||||
/*
|
||||
Copyright 2018 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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 agentconn
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
)
|
||||
|
||||
// Dial creates net.Conn to a SSH agent listening on a Unix socket.
|
||||
func Dial(socket string) (net.Conn, error) {
|
||||
conn, err := net.Dial("unix", socket)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// +build windows
|
||||
|
||||
/*
|
||||
Copyright 2018 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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 agentconn
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
"github.com/Microsoft/go-winio"
|
||||
)
|
||||
|
||||
// Dial creates net.Conn to a SSH agent listening on a Windows named pipe.
|
||||
// This is behind a build flag because winio.DialPipe is only available on
|
||||
// Windows.
|
||||
func Dial(socket string) (net.Conn, error) {
|
||||
conn, err := winio.DialPipe(defaults.WindowsOpenSSHNamedPipe, nil)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package agentconn
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
const SocketEnvironmentVariableName = "SSH_AUTH_SOCK"
|
||||
|
||||
func DialDefaultAgent() (net.Conn, error) {
|
||||
socket := os.Getenv(SocketEnvironmentVariableName)
|
||||
|
||||
if socket == "" {
|
||||
return nil, fmt.Errorf("%s is not set")
|
||||
}
|
||||
|
||||
return dialAgent(socket)
|
||||
}
|
||||
|
||||
func DialAgent(socket string) (net.Conn, error) {
|
||||
return dialAgent(socket)
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
// +build !windows
|
||||
|
||||
package agentconn
|
||||
|
||||
import "net"
|
||||
|
||||
func dialAgent(socket string) (net.Conn, error) {
|
||||
return net.Dial("unix", socket)
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
package agentconn
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
"github.com/Microsoft/go-winio"
|
||||
)
|
||||
|
||||
// The Cygwin support below was originally based on this implementation:
|
||||
// https://github.com/abourget/secrets-bridge/blob/master/pkg/agentfwd/agentconn_windows.go
|
||||
|
||||
const microsoftOpenSSHAgentPipe = "\\\\.\\pipe\\openssh-ssh-agent"
|
||||
|
||||
var windowsFakeSocket = regexp.MustCompile(`!<socket >(\d+) ([A-Fa-f0-9-]+)`)
|
||||
|
||||
func dialAgent(socket string) (net.Conn, error) {
|
||||
// If the socket path isn't set, or is set to the named pipe associated with
|
||||
// Microsoft's native OpenSSH implementation, try connecting with a native
|
||||
// Windows named pipe.
|
||||
if socket == "" || socket == microsoftOpenSSHAgentPipe {
|
||||
return winio.DialPipe(microsoftOpenSSHAgentPipe, nil)
|
||||
}
|
||||
|
||||
// If a socket path was specified, fall back to Cygwin's Unix socket emulation
|
||||
socketFileData, err := ioutil.ReadFile(socket)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
matches := windowsFakeSocket.FindStringSubmatch(string(socketFileData))
|
||||
if matches == nil {
|
||||
return nil, fmt.Errorf("couldn't parse SSH_AUTH_SOCK file %s", socket)
|
||||
}
|
||||
|
||||
tcpPort := matches[1]
|
||||
key := matches[2]
|
||||
|
||||
conn, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%s", tcpPort))
|
||||
if err != nil {
|
||||
return conn, err
|
||||
}
|
||||
|
||||
b := make([]byte, 16)
|
||||
fmt.Sscanf(key,
|
||||
"%02x%02x%02x%02x-%02x%02x%02x%02x-%02x%02x%02x%02x-%02x%02x%02x%02x",
|
||||
&b[3], &b[2], &b[1], &b[0],
|
||||
&b[7], &b[6], &b[5], &b[4],
|
||||
&b[11], &b[10], &b[9], &b[8],
|
||||
&b[15], &b[14], &b[13], &b[12],
|
||||
)
|
||||
|
||||
if _, err = conn.Write(b); err != nil {
|
||||
return nil, fmt.Errorf("write b: %v", err)
|
||||
}
|
||||
|
||||
b2 := make([]byte, 16)
|
||||
if _, err = conn.Read(b2); err != nil {
|
||||
return nil, fmt.Errorf("read b2: %v", err)
|
||||
}
|
||||
|
||||
pidsUids := make([]byte, 12)
|
||||
pid := os.Getpid()
|
||||
uid := 0
|
||||
gid := pid // for cygwin's AF_UNIX -> AF_INET, pid = gid
|
||||
|
||||
binary.LittleEndian.PutUint32(pidsUids, uint32(pid))
|
||||
binary.LittleEndian.PutUint32(pidsUids[4:], uint32(uid))
|
||||
binary.LittleEndian.PutUint32(pidsUids[8:], uint32(gid))
|
||||
|
||||
if _, err = conn.Write(pidsUids); err != nil {
|
||||
return nil, fmt.Errorf("write pid,uid,gid: %v", err)
|
||||
}
|
||||
|
||||
b3 := make([]byte, 12)
|
||||
if _, err = conn.Read(b3); err != nil {
|
||||
return nil, fmt.Errorf("read pid,uid,gid: %v", err)
|
||||
}
|
||||
|
||||
return conn, nil
|
||||
}
|
||||
+7
-1
@@ -49,7 +49,13 @@ func InitLogger(purpose LoggingPurpose, level log.Level) {
|
||||
|
||||
switch purpose {
|
||||
case LoggingForCLI:
|
||||
SwitchLoggingtoSyslog()
|
||||
// If debug logging was asked for on the CLI, then write logs to stderr.
|
||||
// Otherwise discard all logs.
|
||||
if level == log.DebugLevel {
|
||||
log.SetOutput(os.Stderr)
|
||||
} else {
|
||||
log.SetOutput(ioutil.Discard)
|
||||
}
|
||||
case LoggingForDaemon:
|
||||
log.SetOutput(os.Stderr)
|
||||
case LoggingForTests:
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
// +build windows
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func SwitchLoggingtoSyslog() {
|
||||
// syslog is not available on windows
|
||||
log.SetOutput(os.Stderr)
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
// +build !windows,!nacl,!plan9
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"log/syslog"
|
||||
"os"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
logrusSyslog "github.com/sirupsen/logrus/hooks/syslog"
|
||||
)
|
||||
|
||||
// SwitchLoggingtoSyslog tells the logger to send the output to syslog
|
||||
func SwitchLoggingtoSyslog() {
|
||||
log.StandardLogger().SetHooks(make(log.LevelHooks))
|
||||
hook, err := logrusSyslog.NewSyslogHook("", "", syslog.LOG_WARNING, "")
|
||||
if err != nil {
|
||||
// syslog not available
|
||||
log.SetOutput(os.Stderr)
|
||||
} else {
|
||||
// ... and disable stderr:
|
||||
log.AddHook(hook)
|
||||
log.SetOutput(ioutil.Discard)
|
||||
}
|
||||
}
|
||||
+15
-1
@@ -20,6 +20,7 @@ import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/gravitational/teleport"
|
||||
"github.com/gravitational/trace"
|
||||
@@ -34,7 +35,7 @@ import (
|
||||
// It also makes sure that base dir exists
|
||||
func EnsureLocalPath(customPath string, defaultLocalDir, defaultLocalPath string) (string, error) {
|
||||
if customPath == "" {
|
||||
homeDir := os.Getenv(teleport.EnvHome)
|
||||
homeDir := getHomeDir()
|
||||
if homeDir == "" {
|
||||
return "", trace.BadParameter("no path provided and environment variable %v is not not set", teleport.EnvHome)
|
||||
}
|
||||
@@ -156,3 +157,16 @@ func StatDir(path string) (os.FileInfo, error) {
|
||||
}
|
||||
return fi, nil
|
||||
}
|
||||
|
||||
// getHomeDir returns the home directory based off the OS.
|
||||
func getHomeDir() string {
|
||||
switch runtime.GOOS {
|
||||
case teleport.LinuxOS:
|
||||
return os.Getenv(teleport.EnvHome)
|
||||
case teleport.DarwinOS:
|
||||
return os.Getenv(teleport.EnvHome)
|
||||
case teleport.WindowsOS:
|
||||
return os.Getenv(teleport.EnvUserProfile)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
+19
-3
@@ -1,12 +1,28 @@
|
||||
// +build !windows
|
||||
|
||||
/*
|
||||
Copyright 2018 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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 (
|
||||
"os"
|
||||
"syscall"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/gravitational/trace"
|
||||
)
|
||||
|
||||
// FSWriteLock grabs Flock-style filesystem lock on an open file
|
||||
|
||||
+31
-7
@@ -1,21 +1,45 @@
|
||||
// +build windows
|
||||
|
||||
package utils
|
||||
|
||||
import "os"
|
||||
/*
|
||||
Copyright 2018 Gravitational, Inc.
|
||||
|
||||
// For the moment, these functions are no-ops that should be replaced with a
|
||||
// native implementation in the future.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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.
|
||||
*/
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
)
|
||||
|
||||
// FSWriteLock not supported on Windows.
|
||||
func FSWriteLock(f *os.File) error {
|
||||
return nil
|
||||
return trace.BadParameter("file locking not supported on Windows")
|
||||
}
|
||||
|
||||
// FSTryWriteLock not supported on Windows.
|
||||
func FSTryWriteLock(f *os.File) error {
|
||||
return nil
|
||||
return trace.BadParameter("file locking not supported on Windows")
|
||||
}
|
||||
|
||||
// FSReadLock not supported on Windows.
|
||||
func FSReadLock(f *os.File) error {
|
||||
return nil
|
||||
return trace.BadParameter("file locking not supported on Windows")
|
||||
}
|
||||
|
||||
// FSUnlock not supported on Windows.
|
||||
func FSUnlock(f *os.File) error {
|
||||
return nil
|
||||
return trace.BadParameter("file locking not supported on Windows")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
// +build !windows
|
||||
|
||||
/*
|
||||
Copyright 2018 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
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 (
|
||||
"io/ioutil"
|
||||
"log/syslog"
|
||||
"os"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
logrusSyslog "github.com/sirupsen/logrus/hooks/syslog"
|
||||
)
|
||||
|
||||
// SwitchLoggingtoSyslog tells the logger to send the output to syslog. This
|
||||
// code is behind a build flag because Windows does not support syslog.
|
||||
func SwitchLoggingtoSyslog() {
|
||||
log.StandardLogger().SetHooks(make(log.LevelHooks))
|
||||
hook, err := logrusSyslog.NewSyslogHook("", "", syslog.LOG_WARNING, "")
|
||||
if err != nil {
|
||||
// syslog not available
|
||||
log.SetOutput(os.Stderr)
|
||||
} else {
|
||||
// ... and disable stderr:
|
||||
log.AddHook(hook)
|
||||
log.SetOutput(ioutil.Discard)
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@ import (
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"runtime"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
@@ -245,6 +246,16 @@ func Run(args []string, underTest bool) {
|
||||
// about the certificate.
|
||||
status := app.Command("status", "Display the list of proxy servers and retrieved certificates")
|
||||
|
||||
// On Windows, hide the "ssh", "join", "play", "scp", and "bench" commands
|
||||
// because they all use a terminal.
|
||||
if runtime.GOOS == teleport.WindowsOS {
|
||||
ssh.Hidden()
|
||||
join.Hidden()
|
||||
play.Hidden()
|
||||
scp.Hidden()
|
||||
bench.Hidden()
|
||||
}
|
||||
|
||||
// parse CLI commands+flags:
|
||||
command, err := app.Parse(args)
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user