certificate auth: fix source address checking inside crypto/ssh

So we can avoid to check source address ourself

81aafe6d26

Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
Nicola Murino
2020-05-16 15:15:32 +02:00
parent 7ae8b2cdeb
commit 469d36d979
6 changed files with 26 additions and 50 deletions

8
go.mod
View File

@@ -34,9 +34,9 @@ require (
github.com/spf13/viper v1.6.3 github.com/spf13/viper v1.6.3
github.com/stretchr/testify v1.5.1 github.com/stretchr/testify v1.5.1
go.etcd.io/bbolt v1.3.4 go.etcd.io/bbolt v1.3.4
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59 golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9
golang.org/x/tools v0.0.0-20200403170748-4480df5f1627 // indirect golang.org/x/tools v0.0.0-20200515220128-d3bf790afa53 // indirect
google.golang.org/api v0.20.0 google.golang.org/api v0.20.0
google.golang.org/genproto v0.0.0-20200403120447-c50568487044 // indirect google.golang.org/genproto v0.0.0-20200403120447-c50568487044 // indirect
gopkg.in/ini.v1 v1.55.0 // indirect gopkg.in/ini.v1 v1.55.0 // indirect
@@ -45,5 +45,5 @@ require (
replace ( replace (
github.com/pkg/sftp => github.com/drakkan/sftp v0.0.0-20200319122022-2fc68482d27f github.com/pkg/sftp => github.com/drakkan/sftp v0.0.0-20200319122022-2fc68482d27f
golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20200409210311-95730af1ff98 golang.org/x/crypto => github.com/drakkan/crypto v0.0.0-20200516130408-81aafe6d26b9
) )

9
go.sum
View File

@@ -63,8 +63,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/drakkan/crypto v0.0.0-20200409210311-95730af1ff98 h1:5yTRWoqE1GhPfctw9E0wh1d3+xHajMyhQAQuxuNqwQ8= github.com/drakkan/crypto v0.0.0-20200516130408-81aafe6d26b9 h1:H+Pr8rDH54EXtBEA5ejYdO9yhFC2xKlCRidiyseuIAg=
github.com/drakkan/crypto v0.0.0-20200409210311-95730af1ff98/go.mod h1:v3bhWOXGYda7H5d2s5t9XA6th3fxW3s0MQxU1R96G/w= github.com/drakkan/crypto v0.0.0-20200516130408-81aafe6d26b9/go.mod h1:v3bhWOXGYda7H5d2s5t9XA6th3fxW3s0MQxU1R96G/w=
github.com/drakkan/sftp v0.0.0-20200319122022-2fc68482d27f h1:hD7R/tb8VF5Lwj0J3PnPZwN1twzpwWS2QJQYkliJ/Jw= github.com/drakkan/sftp v0.0.0-20200319122022-2fc68482d27f h1:hD7R/tb8VF5Lwj0J3PnPZwN1twzpwWS2QJQYkliJ/Jw=
github.com/drakkan/sftp v0.0.0-20200319122022-2fc68482d27f/go.mod h1:PIrgHN0+qgDmYTNiwryjoEqmXo9tv8aMwQ//Yg1xwIs= github.com/drakkan/sftp v0.0.0-20200319122022-2fc68482d27f/go.mod h1:PIrgHN0+qgDmYTNiwryjoEqmXo9tv8aMwQ//Yg1xwIs=
github.com/eikenb/pipeat v0.0.0-20200430215831-470df5986b6d h1:8RvCRWer7TB2n+DKhW4uW15hRiqPmabSnSyYhju/Nuw= github.com/eikenb/pipeat v0.0.0-20200430215831-470df5986b6d h1:8RvCRWer7TB2n+DKhW4uW15hRiqPmabSnSyYhju/Nuw=
@@ -367,8 +367,9 @@ golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d h1:nc5K6ox/4lTFbMVSL9WRR81ixkcwXThoiF6yf+R9scA=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9 h1:YTzHMGlqJu67/uEo1lBv0n3wBXhXNeUbB1XfN2vmTm0=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -411,7 +412,7 @@ golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200403170748-4480df5f1627/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515220128-d3bf790afa53/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@@ -1739,6 +1739,9 @@ func TestProxyProtocolVersion(t *testing.T) {
func TestLoadHostKeys(t *testing.T) { func TestLoadHostKeys(t *testing.T) {
c := Configuration{} c := Configuration{}
c.Keys = []Key{ c.Keys = []Key{
{
PrivateKey: ".",
},
{ {
PrivateKey: "missing file", PrivateKey: "missing file",
}, },
@@ -1761,7 +1764,7 @@ func TestLoadHostKeys(t *testing.T) {
func TestCertCheckerInitErrors(t *testing.T) { func TestCertCheckerInitErrors(t *testing.T) {
c := Configuration{} c := Configuration{}
c.TrustedUserCAKeys = append(c.TrustedUserCAKeys, "missing file") c.TrustedUserCAKeys = []string{".", "missing file"}
err := c.initializeCertChecker("") err := c.initializeCertChecker("")
assert.Error(t, err) assert.Error(t, err)
testfile := filepath.Join(os.TempDir(), "invalidkey") testfile := filepath.Join(os.TempDir(), "invalidkey")

View File

@@ -167,7 +167,7 @@ func (c Configuration) Initialize(configDir string) error {
PublicKeyCallback: func(conn ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) { PublicKeyCallback: func(conn ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
sp, err := c.validatePublicKeyCredentials(conn, pubKey) sp, err := c.validatePublicKeyCredentials(conn, pubKey)
if err == ssh.ErrPartialSuccess { if err == ssh.ErrPartialSuccess {
return nil, err return sp, err
} }
if err != nil { if err != nil {
return nil, &authenticationError{err: fmt.Sprintf("could not validate public key credentials: %v", err)} return nil, &authenticationError{err: fmt.Sprintf("could not validate public key credentials: %v", err)}
@@ -530,6 +530,11 @@ func (c *Configuration) checkAndLoadHostKeys(configDir string, serverConfig *ssh
} }
for _, k := range c.Keys { for _, k := range c.Keys {
privateFile := k.PrivateKey privateFile := k.PrivateKey
if !utils.IsFileInputValid(privateFile) {
logger.Warn(logSender, "", "unable to load invalid host key: %#v", privateFile)
logger.WarnToConsole("unable to load invalid host key: %#v", privateFile)
continue
}
if !filepath.IsAbs(privateFile) { if !filepath.IsAbs(privateFile) {
privateFile = filepath.Join(configDir, privateFile) privateFile = filepath.Join(configDir, privateFile)
} }
@@ -553,6 +558,11 @@ func (c *Configuration) checkAndLoadHostKeys(configDir string, serverConfig *ssh
func (c *Configuration) initializeCertChecker(configDir string) error { func (c *Configuration) initializeCertChecker(configDir string) error {
for _, keyPath := range c.TrustedUserCAKeys { for _, keyPath := range c.TrustedUserCAKeys {
if !utils.IsFileInputValid(keyPath) {
logger.Warn(logSender, "", "unable to load invalid trusted user CA key: %#v", keyPath)
logger.WarnToConsole("unable to load invalid trusted user CA key: %#v", keyPath)
continue
}
if !filepath.IsAbs(keyPath) { if !filepath.IsAbs(keyPath) {
keyPath = filepath.Join(configDir, keyPath) keyPath = filepath.Join(configDir, keyPath)
} }
@@ -611,19 +621,12 @@ func (c Configuration) validatePublicKeyCredentials(conn ssh.ConnMetadata, pubKe
updateLoginMetrics(conn, method, err) updateLoginMetrics(conn, method, err)
return nil, err return nil, err
} }
// we need to check source address ourself since crypto/ssh will skip this check if we return partial success
if cert.Permissions.CriticalOptions != nil && cert.Permissions.CriticalOptions[sourceAddressCriticalOption] != "" {
if err := utils.CheckSourceAddress(conn.RemoteAddr(), cert.Permissions.CriticalOptions[sourceAddressCriticalOption]); err != nil {
updateLoginMetrics(conn, method, err)
return nil, err
}
}
certPerm = &cert.Permissions certPerm = &cert.Permissions
} }
if user, keyID, err = dataprovider.CheckUserAndPubKey(dataProvider, conn.User(), pubKey.Marshal()); err == nil { if user, keyID, err = dataprovider.CheckUserAndPubKey(dataProvider, conn.User(), pubKey.Marshal()); err == nil {
if user.IsPartialAuth(method) { if user.IsPartialAuth(method) {
logger.Debug(logSender, connectionID, "user %#v authenticated with partial success", conn.User()) logger.Debug(logSender, connectionID, "user %#v authenticated with partial success", conn.User())
return nil, ssh.ErrPartialSuccess return certPerm, ssh.ErrPartialSuccess
} }
sshPerm, err = loginUser(user, method, keyID, conn) sshPerm, err = loginUser(user, method, keyID, conn)
if err == nil && certPerm != nil { if err == nil && certPerm != nil {

View File

@@ -833,7 +833,7 @@ func TestLoginUserCert(t *testing.T) {
err = os.RemoveAll(user.GetHomeDir()) err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err) assert.NoError(t, err)
// now login with a username not in the set of valid principals for given certificate // now login with a username not in the set of valid principals for the given certificate
u.Username += "1" u.Username += "1"
user, _, err = httpd.AddUser(u, http.StatusOK) user, _, err = httpd.AddUser(u, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)

View File

@@ -317,34 +317,3 @@ func CleanDirInput(dirInput string) string {
} }
return filepath.Clean(dirInput) return filepath.Clean(dirInput)
} }
// CheckSourceAddress check the source address against the one defined inside an SSH user certificate
func CheckSourceAddress(addr net.Addr, sourceAddrs string) error {
if addr == nil {
return errors.New("ssh: no address known for client, but source-address match required")
}
tcpAddr, ok := addr.(*net.TCPAddr)
if !ok {
return fmt.Errorf("ssh: remote address %v is not an TCP address when checking source-address match", addr)
}
for _, sourceAddr := range strings.Split(sourceAddrs, ",") {
if allowedIP := net.ParseIP(sourceAddr); allowedIP != nil {
if allowedIP.Equal(tcpAddr.IP) {
return nil
}
} else {
_, ipNet, err := net.ParseCIDR(sourceAddr)
if err != nil {
return fmt.Errorf("ssh: error parsing source-address restriction %q: %v", sourceAddr, err)
}
if ipNet.Contains(tcpAddr.IP) {
return nil
}
}
}
return fmt.Errorf("ssh: remote address %v is not allowed because of source-address restriction", addr)
}