AUTH-2018: Adds support for authorized keys and short lived certs
This commit is contained in:
parent
df25ed9bde
commit
baec3e289e
|
@ -373,9 +373,12 @@
|
||||||
version = "v2.4"
|
version = "v2.4"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
digest = "1:04457f9f6f3ffc5fea48e71d62f2ca256637dee0a04d710288e27e05c8b41976"
|
digest = "1:31538f8d774e8bf2fb9380d2d2a82d69e206a7d0603946586926f1819c546c26"
|
||||||
name = "github.com/sirupsen/logrus"
|
name = "github.com/sirupsen/logrus"
|
||||||
packages = ["."]
|
packages = [
|
||||||
|
".",
|
||||||
|
"hooks/test",
|
||||||
|
]
|
||||||
pruneopts = "UT"
|
pruneopts = "UT"
|
||||||
revision = "839c75faf7f98a33d445d181f3018b5c3409a45e"
|
revision = "839c75faf7f98a33d445d181f3018b5c3409a45e"
|
||||||
version = "v1.4.2"
|
version = "v1.4.2"
|
||||||
|
@ -607,6 +610,7 @@
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp",
|
"github.com/prometheus/client_golang/prometheus/promhttp",
|
||||||
"github.com/rifflock/lfshook",
|
"github.com/rifflock/lfshook",
|
||||||
"github.com/sirupsen/logrus",
|
"github.com/sirupsen/logrus",
|
||||||
|
"github.com/sirupsen/logrus/hooks/test",
|
||||||
"github.com/stretchr/testify/assert",
|
"github.com/stretchr/testify/assert",
|
||||||
"github.com/stretchr/testify/require",
|
"github.com/stretchr/testify/require",
|
||||||
"golang.org/x/crypto/nacl/box",
|
"golang.org/x/crypto/nacl/box",
|
||||||
|
|
|
@ -338,7 +338,7 @@ func StartServer(c *cli.Context, version string, shutdownC, graceShutdownC chan
|
||||||
logger.Infof("ssh-server set")
|
logger.Infof("ssh-server set")
|
||||||
|
|
||||||
sshServerAddress := "127.0.0.1:" + c.String("local-ssh-port")
|
sshServerAddress := "127.0.0.1:" + c.String("local-ssh-port")
|
||||||
server, err := sshserver.New(logger, sshServerAddress, shutdownC)
|
server, err := sshserver.New(logger, sshServerAddress, shutdownC, c.Bool("short-lived-certs"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.WithError(err).Error("Cannot create new SSH Server")
|
logger.WithError(err).Error("Cannot create new SSH Server")
|
||||||
return errors.Wrap(err, "Cannot create new SSH Server")
|
return errors.Wrap(err, "Cannot create new SSH Server")
|
||||||
|
@ -915,5 +915,11 @@ func tunnelFlags(shouldHide bool) []cli.Flag {
|
||||||
EnvVars: []string{"LOCAL_SSH_PORT"},
|
EnvVars: []string{"LOCAL_SSH_PORT"},
|
||||||
Hidden: true,
|
Hidden: true,
|
||||||
}),
|
}),
|
||||||
|
altsrc.NewBoolFlag(&cli.BoolFlag{
|
||||||
|
Name: "short-lived-certs",
|
||||||
|
Usage: "Enable short lived cert authentication for SSH server",
|
||||||
|
EnvVars: []string{"SHORT_LIVED_CERTS"},
|
||||||
|
Hidden: true,
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
//+build !windows
|
||||||
|
|
||||||
|
package sshserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"github.com/gliderlabs/ssh"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
gossh "golang.org/x/crypto/ssh"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
systemConfigPath = "/etc/cloudflared/"
|
||||||
|
authorizeKeysPath = ".cloudflared/authorized_keys"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *SSHServer) authorizedKeyHandler(ctx ssh.Context, key ssh.PublicKey) bool {
|
||||||
|
sshUser, err := s.getUserFunc(ctx.User())
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Debugf("Invalid user: %s", ctx.User())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
authorizedKeysPath := path.Join(sshUser.HomeDir, authorizeKeysPath)
|
||||||
|
if _, err := os.Stat(authorizedKeysPath); os.IsNotExist(err) {
|
||||||
|
s.logger.Debugf("authorized_keys file %s not found", authorizeKeysPath)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
authorizedKeysBytes, err := ioutil.ReadFile(authorizedKeysPath)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.WithError(err).Errorf("Failed to load authorized_keys %s", authorizedKeysPath)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for len(authorizedKeysBytes) > 0 {
|
||||||
|
// Skips invalid keys. Returns error if no valid keys remain.
|
||||||
|
pubKey, _, _, rest, err := ssh.ParseAuthorizedKey(authorizedKeysBytes)
|
||||||
|
authorizedKeysBytes = rest
|
||||||
|
if err != nil {
|
||||||
|
s.logger.WithError(err).Errorf("No valid keys found in %s", authorizeKeysPath)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if ssh.KeysEqual(pubKey, key) {
|
||||||
|
ctx.SetValue("sshUser", sshUser)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.logger.Debugf("Matching public key not found in %s", authorizeKeysPath)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SSHServer) shortLivedCertHandler(ctx ssh.Context, key ssh.PublicKey) bool {
|
||||||
|
userCert, ok := key.(*gossh.Certificate)
|
||||||
|
if !ok {
|
||||||
|
s.logger.Debug("Received key is not an SSH certificate")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ssh.KeysEqual(s.caCert, userCert.SignatureKey) {
|
||||||
|
s.logger.Debug("CA certificate does not match user certificate signer")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
checker := gossh.CertChecker{}
|
||||||
|
if err := checker.CheckCert(ctx.User(), userCert); err != nil {
|
||||||
|
s.logger.Debug(err)
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
sshUser, err := s.getUserFunc(ctx.User())
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Debugf("Invalid user: %s", ctx.User())
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
ctx.SetValue("sshUser", sshUser)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func getCACert() (ssh.PublicKey, error) {
|
||||||
|
caCertPath := path.Join(systemConfigPath, "ca.pub")
|
||||||
|
caCertBytes, err := ioutil.ReadFile(caCertPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, fmt.Sprintf("Failed to load CA certertificate %s", caCertPath))
|
||||||
|
}
|
||||||
|
caCert, _, _, _, err := ssh.ParseAuthorizedKey(caCertBytes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "Failed to parse CA Certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
return caCert, nil
|
||||||
|
}
|
|
@ -0,0 +1,192 @@
|
||||||
|
//+build !windows
|
||||||
|
|
||||||
|
package sshserver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"path"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cloudflare/cloudflared/log"
|
||||||
|
"github.com/gliderlabs/ssh"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
"github.com/sirupsen/logrus/hooks/test"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
validPrincipal = "testUser"
|
||||||
|
testDir = "testdata"
|
||||||
|
testUserKeyFilename = "id_rsa.pub"
|
||||||
|
testCAFilename = "ca.pub"
|
||||||
|
testOtherCAFilename = "other_ca.pub"
|
||||||
|
testUserCertFilename = "id_rsa-cert.pub"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logger, hook = test.NewNullLogger()
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
authorizeKeysPath = testUserKeyFilename
|
||||||
|
logger.SetLevel(logrus.DebugLevel)
|
||||||
|
code := m.Run()
|
||||||
|
os.Exit(code)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPublicKeyAuth_Success(t *testing.T) {
|
||||||
|
context, cancel := newMockContext(validPrincipal)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
sshServer := SSHServer{getUserFunc: getMockUser}
|
||||||
|
|
||||||
|
pubKey := getKey(t, testUserKeyFilename)
|
||||||
|
assert.True(t, sshServer.authorizedKeyHandler(context, pubKey))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPublicKeyAuth_MissingKey(t *testing.T) {
|
||||||
|
context, cancel := newMockContext(validPrincipal)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
sshServer := SSHServer{logger: logger, getUserFunc: getMockUser}
|
||||||
|
|
||||||
|
pubKey := getKey(t, testOtherCAFilename)
|
||||||
|
assert.False(t, sshServer.authorizedKeyHandler(context, pubKey))
|
||||||
|
assert.Contains(t, hook.LastEntry().Message, "Matching public key not found in")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPublicKeyAuth_InvalidUser(t *testing.T) {
|
||||||
|
context, cancel := newMockContext("notAUser")
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
sshServer := SSHServer{logger: logger, getUserFunc: lookupUser}
|
||||||
|
|
||||||
|
pubKey := getKey(t, testUserKeyFilename)
|
||||||
|
assert.False(t, sshServer.authorizedKeyHandler(context, pubKey))
|
||||||
|
assert.Contains(t, hook.LastEntry().Message, "Invalid user")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPublicKeyAuth_MissingFile(t *testing.T) {
|
||||||
|
currentUser, err := user.Current()
|
||||||
|
require.Nil(t, err)
|
||||||
|
context, cancel := newMockContext(currentUser.Username)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
sshServer := SSHServer{Server: ssh.Server{}, logger: logger, getUserFunc: lookupUser}
|
||||||
|
|
||||||
|
pubKey := getKey(t, testUserKeyFilename)
|
||||||
|
assert.False(t, sshServer.authorizedKeyHandler(context, pubKey))
|
||||||
|
assert.Contains(t, hook.LastEntry().Message, "not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortLivedCerts_Success(t *testing.T) {
|
||||||
|
context, cancel := newMockContext(validPrincipal)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
caCert := getKey(t, testCAFilename)
|
||||||
|
sshServer := SSHServer{logger: log.CreateLogger(), caCert: caCert, getUserFunc: getMockUser}
|
||||||
|
|
||||||
|
userCert := getKey(t, testUserCertFilename)
|
||||||
|
assert.True(t, sshServer.shortLivedCertHandler(context, userCert))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortLivedCerts_CAsDontMatch(t *testing.T) {
|
||||||
|
context, cancel := newMockContext(validPrincipal)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
caCert := getKey(t, testOtherCAFilename)
|
||||||
|
sshServer := SSHServer{logger: logger, caCert: caCert, getUserFunc: getMockUser}
|
||||||
|
|
||||||
|
userCert := getKey(t, testUserCertFilename)
|
||||||
|
assert.False(t, sshServer.shortLivedCertHandler(context, userCert))
|
||||||
|
assert.Equal(t, "CA certificate does not match user certificate signer", hook.LastEntry().Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortLivedCerts_UserDoesNotExist(t *testing.T) {
|
||||||
|
context, cancel := newMockContext(validPrincipal)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
caCert := getKey(t, testCAFilename)
|
||||||
|
sshServer := SSHServer{logger: logger, caCert: caCert, getUserFunc: lookupUser}
|
||||||
|
|
||||||
|
userCert := getKey(t, testUserCertFilename)
|
||||||
|
assert.False(t, sshServer.shortLivedCertHandler(context, userCert))
|
||||||
|
assert.Contains(t, hook.LastEntry().Message, "Invalid user")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestShortLivedCerts_InvalidPrincipal(t *testing.T) {
|
||||||
|
context, cancel := newMockContext("notAUser")
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
caCert := getKey(t, testCAFilename)
|
||||||
|
sshServer := SSHServer{logger: logger, caCert: caCert, getUserFunc: lookupUser}
|
||||||
|
|
||||||
|
userCert := getKey(t, testUserCertFilename)
|
||||||
|
assert.False(t, sshServer.shortLivedCertHandler(context, userCert))
|
||||||
|
assert.Contains(t, hook.LastEntry().Message, "not in the set of valid principals for given certificate")
|
||||||
|
}
|
||||||
|
|
||||||
|
func getMockUser(_ string) (*User, error) {
|
||||||
|
return &User{
|
||||||
|
Username: validPrincipal,
|
||||||
|
HomeDir: testDir,
|
||||||
|
}, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func getKey(t *testing.T, filename string) ssh.PublicKey {
|
||||||
|
path := path.Join(testDir, filename)
|
||||||
|
bytes, err := ioutil.ReadFile(path)
|
||||||
|
require.Nil(t, err)
|
||||||
|
pubKey, _, _, _, err := ssh.ParseAuthorizedKey(bytes)
|
||||||
|
require.Nil(t, err)
|
||||||
|
return pubKey
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockSSHContext struct {
|
||||||
|
context.Context
|
||||||
|
*sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func newMockContext(user string) (*mockSSHContext, context.CancelFunc) {
|
||||||
|
innerCtx, cancel := context.WithCancel(context.Background())
|
||||||
|
mockCtx := &mockSSHContext{innerCtx, &sync.Mutex{}}
|
||||||
|
mockCtx.SetValue("user", user)
|
||||||
|
return mockCtx, cancel
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *mockSSHContext) SetValue(key, value interface{}) {
|
||||||
|
ctx.Context = context.WithValue(ctx.Context, key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *mockSSHContext) User() string {
|
||||||
|
return ctx.Value("user").(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *mockSSHContext) SessionID() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *mockSSHContext) ClientVersion() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *mockSSHContext) ServerVersion() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *mockSSHContext) RemoteAddr() net.Addr {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *mockSSHContext) LocalAddr() net.Addr {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *mockSSHContext) Permissions() *ssh.Permissions {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
// Taken from https://github.com/golang/go/blob/ad644d2e86bab85787879d41c2d2aebbd7c57db8/src/os/user/user.go
|
// Taken from https://github.com/golang/go/blob/ad644d2e86bab85787879d41c2d2aebbd7c57db8/src/os/user/user.go
|
||||||
// and modified to return login shell in User struct. cloudflared requires cgo for compilation because of this addition.
|
// and modified to return login shell in User struct. cloudflared requires cgo for compilation because of this addition.
|
||||||
|
|
||||||
|
|
||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
// Copyright 2011 The Go Authors. All rights reserved.
|
||||||
// Use of this source code is governed by a BSD-style
|
// Use of this source code is governed by a BSD-style
|
||||||
// license that can be found in the LICENSE file.
|
// license that can be found in the LICENSE file.
|
||||||
|
|
|
@ -24,9 +24,9 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *SSHServer) configureHostKeys() error {
|
func (s *SSHServer) configureHostKeys() error {
|
||||||
if _, err := os.Stat(configDir); os.IsNotExist(err) {
|
if _, err := os.Stat(systemConfigPath); os.IsNotExist(err) {
|
||||||
if err := os.MkdirAll(configDir, 0755); err != nil {
|
if err := os.MkdirAll(systemConfigPath, 0755); err != nil {
|
||||||
return errors.Wrap(err, fmt.Sprintf("Error creating %s directory", configDir))
|
return errors.Wrap(err, fmt.Sprintf("Error creating %s directory", systemConfigPath))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ func (s *SSHServer) configureHostKey(keyFunc func() (string, error)) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SSHServer) ensureRSAKeyExists() (string, error) {
|
func (s *SSHServer) ensureRSAKeyExists() (string, error) {
|
||||||
keyPath := filepath.Join(configDir, rsaFilename)
|
keyPath := filepath.Join(systemConfigPath, rsaFilename)
|
||||||
if _, err := os.Stat(keyPath); os.IsNotExist(err) {
|
if _, err := os.Stat(keyPath); os.IsNotExist(err) {
|
||||||
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
key, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -76,7 +76,7 @@ func (s *SSHServer) ensureRSAKeyExists() (string, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SSHServer) ensureECDSAKeyExists() (string, error) {
|
func (s *SSHServer) ensureECDSAKeyExists() (string, error) {
|
||||||
keyPath := filepath.Join(configDir, ecdsaFilename)
|
keyPath := filepath.Join(systemConfigPath, ecdsaFilename)
|
||||||
if _, err := os.Stat(keyPath); os.IsNotExist(err) {
|
if _, err := os.Stat(keyPath); os.IsNotExist(err) {
|
||||||
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -4,8 +4,8 @@ package sshserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
@ -19,18 +19,15 @@ import (
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
defaultShellPrompt = `\e[0;31m[\u@\h \W]\$ \e[m `
|
|
||||||
configDir = "/etc/cloudflared/"
|
|
||||||
)
|
|
||||||
|
|
||||||
type SSHServer struct {
|
type SSHServer struct {
|
||||||
ssh.Server
|
ssh.Server
|
||||||
logger *logrus.Logger
|
logger *logrus.Logger
|
||||||
shutdownC chan struct{}
|
shutdownC chan struct{}
|
||||||
|
caCert ssh.PublicKey
|
||||||
|
getUserFunc func(string) (*User, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(logger *logrus.Logger, address string, shutdownC chan struct{}) (*SSHServer, error) {
|
func New(logger *logrus.Logger, address string, shutdownC chan struct{}, shortLivedCertAuth bool) (*SSHServer, error) {
|
||||||
currentUser, err := user.Current()
|
currentUser, err := user.Current()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -39,11 +36,28 @@ func New(logger *logrus.Logger, address string, shutdownC chan struct{}) (*SSHSe
|
||||||
return nil, errors.New("cloudflared SSH server needs to run as root")
|
return nil, errors.New("cloudflared SSH server needs to run as root")
|
||||||
}
|
}
|
||||||
|
|
||||||
sshServer := SSHServer{ssh.Server{Addr: address}, logger, shutdownC}
|
sshServer := SSHServer{
|
||||||
|
Server: ssh.Server{Addr: address},
|
||||||
|
logger: logger,
|
||||||
|
shutdownC: shutdownC,
|
||||||
|
getUserFunc: lookupUser,
|
||||||
|
}
|
||||||
|
|
||||||
if err := sshServer.configureHostKeys(); err != nil {
|
if err := sshServer.configureHostKeys(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if shortLivedCertAuth {
|
||||||
|
caCert, err := getCACert()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
sshServer.caCert = caCert
|
||||||
|
sshServer.PublicKeyHandler = sshServer.shortLivedCertHandler
|
||||||
|
} else {
|
||||||
|
sshServer.PublicKeyHandler = sshServer.authorizedKeyHandler
|
||||||
|
}
|
||||||
|
|
||||||
return &sshServer, nil
|
return &sshServer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,11 +78,9 @@ func (s *SSHServer) Start() error {
|
||||||
func (s *SSHServer) connectionHandler(session ssh.Session) {
|
func (s *SSHServer) connectionHandler(session ssh.Session) {
|
||||||
|
|
||||||
// Get uid and gid of user attempting to login
|
// Get uid and gid of user attempting to login
|
||||||
sshUser, err := lookupUser(session.User())
|
sshUser, ok := session.Context().Value("sshUser").(*User)
|
||||||
if err != nil {
|
if !ok || sshUser == nil {
|
||||||
if _, err := io.WriteString(session, "Invalid credentials\n"); err != nil {
|
s.logger.Error("Error retrieving credentials from session")
|
||||||
s.logger.WithError(err).Error("Invalid credentials: Failed to write to SSH session")
|
|
||||||
}
|
|
||||||
s.CloseSession(session)
|
s.CloseSession(session)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -86,17 +98,22 @@ func (s *SSHServer) connectionHandler(session ssh.Session) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
uidInt, uidErr := stringToUint32(sshUser.Uid)
|
uidInt, err := stringToUint32(sshUser.Uid)
|
||||||
gidInt, gidErr := stringToUint32(sshUser.Gid)
|
if err != nil {
|
||||||
if uidErr != nil || gidErr != nil {
|
|
||||||
s.logger.WithError(err).Error("Invalid user")
|
s.logger.WithError(err).Error("Invalid user")
|
||||||
s.CloseSession(session)
|
s.CloseSession(session)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
gidInt, err := stringToUint32(sshUser.Gid)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.WithError(err).Error("Invalid user group")
|
||||||
|
s.CloseSession(session)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{Credential: &syscall.Credential{Uid: uidInt, Gid: gidInt}}
|
cmd.SysProcAttr = &syscall.SysProcAttr{Credential: &syscall.Credential{Uid: uidInt, Gid: gidInt}}
|
||||||
cmd.Env = append(cmd.Env, fmt.Sprintf("TERM=%s", ptyReq.Term))
|
cmd.Env = append(cmd.Env, fmt.Sprintf("TERM=%s", ptyReq.Term))
|
||||||
cmd.Env = append(cmd.Env, fmt.Sprintf("USER=%s", sshUser.Name))
|
cmd.Env = append(cmd.Env, fmt.Sprintf("USER=%s", sshUser.Username))
|
||||||
cmd.Env = append(cmd.Env, fmt.Sprintf("HOME=%s", sshUser.HomeDir))
|
cmd.Env = append(cmd.Env, fmt.Sprintf("HOME=%s", sshUser.HomeDir))
|
||||||
cmd.Dir = sshUser.HomeDir
|
cmd.Dir = sshUser.HomeDir
|
||||||
psuedoTTY, err := pty.Start(cmd)
|
psuedoTTY, err := pty.Start(cmd)
|
||||||
|
|
|
@ -3,15 +3,17 @@
|
||||||
package sshserver
|
package sshserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SSHServer struct{}
|
type SSHServer struct{}
|
||||||
|
|
||||||
func New(_ *logrus.Logger, _ string, _ chan struct{}) (*SSHServer, error) {
|
func New(_ *logrus.Logger, _ string, _ chan struct{}, _ bool) (*SSHServer, error) {
|
||||||
return nil, nil
|
return nil, errors.New("cloudflared ssh server is not supported on windows")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SSHServer) Start() error {
|
func (s *SSHServer) Start() error {
|
||||||
return nil
|
return errors.New("cloudflared ssh server is not supported on windows")
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn
|
||||||
|
NhAAAAAwEAAQAAAQEA0c6EklYvC9B041qEGWDNuot6G4tTVm9LCQC0vA+v2n25ru9CINV6
|
||||||
|
8IljmXBORXBwfG6PdLhg0SEabZUbsNX5WrIVbGovcghKS6GRsqI5+Quhm+o8eG042JE/hB
|
||||||
|
oYdZ19TcMEyPOGzHsx0U/BSN9ZJWVCxqN51iI6qyhz9f6jlX2LQBFEvXlhxgF3owBEf8UC
|
||||||
|
Zt/UvbZdmeeyKNQElPmiVLIJEAPCueECp7a2mjCiP3zqjDvSeeGk4CelB/1qZZ4V2n7fvb
|
||||||
|
HZjAB5JJs4KXs5o8KgvQnqgQMxiLFZ4PATt4+mxEzh4JymppbqJOo2rYwOA3TAIEWWtYRV
|
||||||
|
/ZKJ0AyhhQAAA8gciO8XHIjvFwAAAAdzc2gtcnNhAAABAQDRzoSSVi8L0HTjWoQZYM26i3
|
||||||
|
obi1NWb0sJALS8D6/afbmu70Ig1XrwiWOZcE5FcHB8bo90uGDRIRptlRuw1flashVsai9y
|
||||||
|
CEpLoZGyojn5C6Gb6jx4bTjYkT+EGhh1nX1NwwTI84bMezHRT8FI31klZULGo3nWIjqrKH
|
||||||
|
P1/qOVfYtAEUS9eWHGAXejAER/xQJm39S9tl2Z57Io1ASU+aJUsgkQA8K54QKntraaMKI/
|
||||||
|
fOqMO9J54aTgJ6UH/WplnhXaft+9sdmMAHkkmzgpezmjwqC9CeqBAzGIsVng8BO3j6bETO
|
||||||
|
HgnKamluok6jatjA4DdMAgRZa1hFX9konQDKGFAAAAAwEAAQAAAQEApVzGdKhk8ETevAst
|
||||||
|
rurze6JPHcKUbr3NQE1EJi2fBvCtF0oQrtxTx54h2GAB8Q0MO6bQfsiL1ojm0ZQCfUBJBs
|
||||||
|
jxxb9zoccS98Vilo7ybm5SdBcMjkZX1am1jCMdQCZfCpk4/kGi7yvyOe1IhG01UBodpX5X
|
||||||
|
mwTjhN+fdjW7LSiW6cKPClN49CZKgmtvI27FCt+/TtMzdCXOiJxJ4yZCzCRhSgssV0gWI1
|
||||||
|
0VJr/MHirKUvv/qCLAuOBxIr9UgdduRZUpNX+KS2rfhFEbjnUqc/57aAakpQmuPB5I+s9G
|
||||||
|
DnrF0HSHpq7u1XC1SvYlnFBN/0A7Hw/MX2SaBFH7mc9AAQAAAIAFuTHr6O8tCvWEawfxC0
|
||||||
|
qiAPQ+Yy1vthq5uewmuQujMutUnc9JAUl32PdU1DbS7APC1Dg9XL7SyAB6A+ZpRJRAKgCY
|
||||||
|
SneAKE6hOytH+yM206aekrz6VuZiSpBqpfEqDibVAaZIO8sv/9dtZd6kWemxNErPQoKJey
|
||||||
|
Z7/cuWUWQovAAAAIEA6ugIlVj1irPmElyCCt5YfPv2x8Dl54ELoP/WsffsrPHNQog64hFd
|
||||||
|
ahD7Wq63TA566bN85fkx8OVU5TbbEQmkHgOEV6nDRY2YsBSqIOblA/KehtfdUIqZB0iNBh
|
||||||
|
Gn6TV/z6HwnSR3gKv4b66Gveek6LfRAG3mbsLCgyRAbYgn6YUAAACBAOSlf+n1eh6yjtvF
|
||||||
|
Zecq3Zslj7O8cUs17PQx4vQ7sXNCFrIZdevWPIn9sVrt7/hsTrXunDz6eXCeclB35KZe3H
|
||||||
|
WPVjRoD+xnr5+sXx2qXOnKCR0LdFybso6IR5bXAI6DNSNfP7D9LPEQ+R73Jk0jPuLYzocS
|
||||||
|
iM89KZiuGpzr01gBAAAAEW1pa2VAQzAyWTUwVEdKR0g4AQ==
|
||||||
|
-----END OPENSSH PRIVATE KEY-----
|
|
@ -0,0 +1 @@
|
||||||
|
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDRzoSSVi8L0HTjWoQZYM26i3obi1NWb0sJALS8D6/afbmu70Ig1XrwiWOZcE5FcHB8bo90uGDRIRptlRuw1flashVsai9yCEpLoZGyojn5C6Gb6jx4bTjYkT+EGhh1nX1NwwTI84bMezHRT8FI31klZULGo3nWIjqrKHP1/qOVfYtAEUS9eWHGAXejAER/xQJm39S9tl2Z57Io1ASU+aJUsgkQA8K54QKntraaMKI/fOqMO9J54aTgJ6UH/WplnhXaft+9sdmMAHkkmzgpezmjwqC9CeqBAzGIsVng8BO3j6bETOHgnKamluok6jatjA4DdMAgRZa1hFX9konQDKGF mike@C02Y50TGJGH8
|
|
@ -0,0 +1,49 @@
|
||||||
|
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
-----END OPENSSH PRIVATE KEY-----
|
|
@ -0,0 +1 @@
|
||||||
|
ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgOsuFqKdzp/nC3wQfKVJBdHa8axtGryKplPkDjdSXT4kAAAADAQABAAACAQDrQqd6jzuo+yH7MStYWXu/H3dzt/JPFtn2VQ2pp+BfL8Es4Jz+dBIxyixVIuxCtseWFjN+ZkSFOK5ua5hSsdyGayfWaXUcio/xPloOC01supidLgfmg7hKPX5ceVC1TufvMdeBxr1Kcxa3UWGTHWa+R6I4qzR6ruFXZO4hnF+Zth8hW8kbCsvS5bNtIQTWC/GBNdkfEQKEhDKs2+KWOCLi7YwP9wiLoox5hSdpMvUXE70bgYgWLLu54dHA5k8gUeBib5Wu1ftdp9IXT///MKsLrfPNa3/oDTqEV6k7PZVe+btJ1yRpHuoxcBvuSKmxzT50qT2CBTsWqnFljlxV5mj1GVFWFFcYPLuYAED8UzC+/6pgKAneeVlt0ptqI8tC7xJxOUn2wwjTr9mIRjThuZ5nQBIznaBoMaOsVtue4c1/ldX+it4RSG0KuvIWegjGYqAfOcaNnnmrvsW1MeesAoWYoU/XWzcwshf2jhGC2uI62Xgp6eQh9S6aC2vBj9QaM5o5BQ6tUfVqbnjCRSJUQXRB3cAPwoKX+X/FPn63pw6yln0YiHBrY0GZGM3/BUan5itvTxF7eTE8Jf1uzmIt76Qc3S+4WvtEOj55SiJI+FaXoVSrHZb01mp6SYSAqfegqt9sZowfVUuRopaXNDjEnM/SJp1BshcavIsRgBLJ16Zm2wAAAAAAAAAAAAAAAQAAAA10ZXN0VXNlckB0ZXN0AAAADAAAAAh0ZXN0VXNlcgAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAAAABFwAAAAdzc2gtcnNhAAAAAwEAAQAAAQEA0c6EklYvC9B041qEGWDNuot6G4tTVm9LCQC0vA+v2n25ru9CINV68IljmXBORXBwfG6PdLhg0SEabZUbsNX5WrIVbGovcghKS6GRsqI5+Quhm+o8eG042JE/hBoYdZ19TcMEyPOGzHsx0U/BSN9ZJWVCxqN51iI6qyhz9f6jlX2LQBFEvXlhxgF3owBEf8UCZt/UvbZdmeeyKNQElPmiVLIJEAPCueECp7a2mjCiP3zqjDvSeeGk4CelB/1qZZ4V2n7fvbHZjAB5JJs4KXs5o8KgvQnqgQMxiLFZ4PATt4+mxEzh4JymppbqJOo2rYwOA3TAIEWWtYRV/ZKJ0AyhhQAAAQ8AAAAHc3NoLXJzYQAAAQC2lL+6JYTGOdz1zNnck6onrFcVpO2onCVAKP8HdLoCeH0/upIugaCocPKuzoURYEfiHQotviNeprE/2CyAroJ5VBdqWftEeHn3FFvBCQ1gwRQ7oci4C5n72t0vjWWE6WBylS0RqpJjr6EQ8a1vuwIqAQrEJPp2yNLjRH2WD7eicBh5f43VKOMr73DtyTh4xoF0C2sNBROudt58npTaYqRHQgoI25V/aCmuYBgM3wdAGcoEZGoSerMfhID7GcWkvemq2hF8mQsspG3zgnyQXk+ahagmefzxutDnr3KdrZ637La0/XwABvBZ9L4l5RiEilVI1Shl96F2qbBW2YZ64pUQ test@cloudflare.com
|
|
@ -0,0 +1 @@
|
||||||
|
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDrQqd6jzuo+yH7MStYWXu/H3dzt/JPFtn2VQ2pp+BfL8Es4Jz+dBIxyixVIuxCtseWFjN+ZkSFOK5ua5hSsdyGayfWaXUcio/xPloOC01supidLgfmg7hKPX5ceVC1TufvMdeBxr1Kcxa3UWGTHWa+R6I4qzR6ruFXZO4hnF+Zth8hW8kbCsvS5bNtIQTWC/GBNdkfEQKEhDKs2+KWOCLi7YwP9wiLoox5hSdpMvUXE70bgYgWLLu54dHA5k8gUeBib5Wu1ftdp9IXT///MKsLrfPNa3/oDTqEV6k7PZVe+btJ1yRpHuoxcBvuSKmxzT50qT2CBTsWqnFljlxV5mj1GVFWFFcYPLuYAED8UzC+/6pgKAneeVlt0ptqI8tC7xJxOUn2wwjTr9mIRjThuZ5nQBIznaBoMaOsVtue4c1/ldX+it4RSG0KuvIWegjGYqAfOcaNnnmrvsW1MeesAoWYoU/XWzcwshf2jhGC2uI62Xgp6eQh9S6aC2vBj9QaM5o5BQ6tUfVqbnjCRSJUQXRB3cAPwoKX+X/FPn63pw6yln0YiHBrY0GZGM3/BUan5itvTxF7eTE8Jf1uzmIt76Qc3S+4WvtEOj55SiJI+FaXoVSrHZb01mp6SYSAqfegqt9sZowfVUuRopaXNDjEnM/SJp1BshcavIsRgBLJ16Zm2w== test@cloudflare.com
|
|
@ -0,0 +1,27 @@
|
||||||
|
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||||
|
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
|
||||||
|
-----END OPENSSH PRIVATE KEY-----
|
|
@ -0,0 +1 @@
|
||||||
|
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDME7tNfFumTuwZCb9Zrb0380V7m66gRAYLmHF+KGZy9WB4p40qJJKyfHMITVjpKY0ygThpCz3bC1yfjwabFHBlJdsKourYImsd970HaTdo+QlmlCQUyyFwkajukbRWWjt1YBth/339gp11/b3S4sru/HD23LSkzWFdJcc1KG/Vv5tNCb8I4xnmM2ydyNzXIEcoaQVUd570OHhnTEuUeKkjLI1dfMjREVi4dI0nP0YPfXRdAsgiuim2vv7/uDr6w88aw2Uj73k5JxtrX89DZv+rL4rIjA0gxJ33OInLnGc7tWjcuupCvNrU+SfaLb0+WNRxtz8XSGFc0iohN01bBWqx mike@C02Y50TGJGH8
|
|
@ -0,0 +1,92 @@
|
||||||
|
// The Test package is used for testing logrus. It is here for backwards
|
||||||
|
// compatibility from when logrus' organization was upper-case. Please use
|
||||||
|
// lower-case logrus and the `null` package instead of this one.
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Hook is a hook designed for dealing with logs in test scenarios.
|
||||||
|
type Hook struct {
|
||||||
|
// Entries is an array of all entries that have been received by this hook.
|
||||||
|
// For safe access, use the AllEntries() method, rather than reading this
|
||||||
|
// value directly.
|
||||||
|
Entries []logrus.Entry
|
||||||
|
mu sync.RWMutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGlobal installs a test hook for the global logger.
|
||||||
|
func NewGlobal() *Hook {
|
||||||
|
|
||||||
|
hook := new(Hook)
|
||||||
|
logrus.AddHook(hook)
|
||||||
|
|
||||||
|
return hook
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewLocal installs a test hook for a given local logger.
|
||||||
|
func NewLocal(logger *logrus.Logger) *Hook {
|
||||||
|
|
||||||
|
hook := new(Hook)
|
||||||
|
logger.Hooks.Add(hook)
|
||||||
|
|
||||||
|
return hook
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewNullLogger creates a discarding logger and installs the test hook.
|
||||||
|
func NewNullLogger() (*logrus.Logger, *Hook) {
|
||||||
|
|
||||||
|
logger := logrus.New()
|
||||||
|
logger.Out = ioutil.Discard
|
||||||
|
|
||||||
|
return logger, NewLocal(logger)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Hook) Fire(e *logrus.Entry) error {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
|
t.Entries = append(t.Entries, *e)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Hook) Levels() []logrus.Level {
|
||||||
|
return logrus.AllLevels
|
||||||
|
}
|
||||||
|
|
||||||
|
// LastEntry returns the last entry that was logged or nil.
|
||||||
|
func (t *Hook) LastEntry() *logrus.Entry {
|
||||||
|
t.mu.RLock()
|
||||||
|
defer t.mu.RUnlock()
|
||||||
|
i := len(t.Entries) - 1
|
||||||
|
if i < 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &t.Entries[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllEntries returns all entries that were logged.
|
||||||
|
func (t *Hook) AllEntries() []*logrus.Entry {
|
||||||
|
t.mu.RLock()
|
||||||
|
defer t.mu.RUnlock()
|
||||||
|
// Make a copy so the returned value won't race with future log requests
|
||||||
|
entries := make([]*logrus.Entry, len(t.Entries))
|
||||||
|
for i := 0; i < len(t.Entries); i++ {
|
||||||
|
// Make a copy, for safety
|
||||||
|
entries[i] = &t.Entries[i]
|
||||||
|
}
|
||||||
|
return entries
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset removes all Entries from this test hook.
|
||||||
|
func (t *Hook) Reset() {
|
||||||
|
t.mu.Lock()
|
||||||
|
defer t.mu.Unlock()
|
||||||
|
t.Entries = make([]logrus.Entry, 0)
|
||||||
|
}
|
Loading…
Reference in New Issue