Added tsh for Windows.

This commit is contained in:
Russell Jones
2018-08-03 11:06:08 -07:00
committed by Russell Jones
parent 7cfc78bafc
commit 87fe7a397d
31 changed files with 680 additions and 541 deletions
+21 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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,
+146
View File
@@ -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) {
-161
View File
@@ -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())
}
}
}()
}
+52 -30
View File
@@ -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
View File
@@ -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:
+4
View File
@@ -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`
+16
View File
@@ -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"
+16
View File
@@ -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 (
+16
View File
@@ -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 (
+20
View File
@@ -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 {
-36
View File
@@ -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
}
-28
View File
@@ -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
View File
@@ -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
}
-11
View File
@@ -1,11 +0,0 @@
// +build !cgo
package shell
const (
DefaultShell = "/bin/sh"
)
func GetLoginShell(username string) (string, error) {
return DefaultShell, nil
}
+88
View File
@@ -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
}
+29
View File
@@ -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")
}
+35
View File
@@ -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
}
+41
View File
@@ -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
}
-23
View File
@@ -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)
}
-9
View File
@@ -1,9 +0,0 @@
// +build !windows
package agentconn
import "net"
func dialAgent(socket string) (net.Conn, error) {
return net.Dial("unix", socket)
}
-85
View File
@@ -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
View File
@@ -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:
-14
View File
@@ -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)
}
-25
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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")
}
+43
View File
@@ -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)
}
}
+11
View File
@@ -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 {