|
|
@ -0,0 +1,174 @@ |
|
|
|
package identity |
|
|
|
|
|
|
|
import ( |
|
|
|
"bytes" |
|
|
|
"errors" |
|
|
|
"fmt" |
|
|
|
|
|
|
|
"golang.org/x/crypto/ssh" |
|
|
|
|
|
|
|
"gitlab.booking.com/pps/lib/auth/staff" |
|
|
|
"maze.io/gate/pkg/core" |
|
|
|
logger2 "maze.io/gate/pkg/core/logger" |
|
|
|
"maze.io/gate/pkg/util/compact" |
|
|
|
) |
|
|
|
|
|
|
|
const ( |
|
|
|
sshUsersGroup = "sshUsers" |
|
|
|
) |
|
|
|
|
|
|
|
type BookingIdentityProvider struct { |
|
|
|
certChecker *ssh.CertChecker |
|
|
|
} |
|
|
|
|
|
|
|
type staffUser struct { |
|
|
|
user *staff.User |
|
|
|
} |
|
|
|
|
|
|
|
type staffGroup struct { |
|
|
|
group *staff.Group |
|
|
|
} |
|
|
|
|
|
|
|
func (u staffUser) ID() compact.ID { return compact.String(u.user.Username) } |
|
|
|
func (u staffUser) Login() string { return u.user.Username } |
|
|
|
func (u staffUser) Name() string { return u.user.Name } |
|
|
|
func (u staffUser) Groups() []core.Group { return nil } |
|
|
|
|
|
|
|
func (g staffGroup) Name() string { return g.group.Name } |
|
|
|
func (g staffGroup) Members() []core.User { return nil } |
|
|
|
|
|
|
|
func NewBookingIdentityProvider(pubKeys []ssh.PublicKey) *BookingIdentityProvider { |
|
|
|
|
|
|
|
CertChecker := &ssh.CertChecker{ |
|
|
|
IsUserAuthority: func(pk ssh.PublicKey) bool { |
|
|
|
for _, pubKey := range pubKeys { |
|
|
|
if bytes.Equal(pk.Marshal(), pubKey.Marshal()) { |
|
|
|
return true |
|
|
|
} |
|
|
|
} |
|
|
|
return false |
|
|
|
}, |
|
|
|
} |
|
|
|
|
|
|
|
return &BookingIdentityProvider{ |
|
|
|
certChecker: CertChecker, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
func (idp *BookingIdentityProvider) LookupUser(name string) (core.User, error) { |
|
|
|
u, err := staff.Lookup(staff.FilterUsername(name)) |
|
|
|
if err != nil { |
|
|
|
return staffUser{}, err |
|
|
|
} |
|
|
|
|
|
|
|
return staffUser{u}, nil |
|
|
|
} |
|
|
|
|
|
|
|
func (idp *BookingIdentityProvider) LookupGroup(name string) (core.Group, error) { |
|
|
|
g, err := staff.LookupGroup(staff.FilterCommonName(name)) |
|
|
|
if err != nil { |
|
|
|
return staffGroup{}, err |
|
|
|
} |
|
|
|
return staffGroup{g}, nil |
|
|
|
} |
|
|
|
|
|
|
|
func (idp *BookingIdentityProvider) PasswordCallback(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) { |
|
|
|
user, err := idp.LookupUser(conn.User()) |
|
|
|
if err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
_ = user |
|
|
|
return nil, errors.New("password not implemented") |
|
|
|
} |
|
|
|
|
|
|
|
func (idp *BookingIdentityProvider) PublicKeyCallback(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) { |
|
|
|
log := logger2.Default.WithFields(logger2.Fields{ |
|
|
|
logger2.User: conn.User(), |
|
|
|
"key": ssh.FingerprintSHA256(key), |
|
|
|
"key_type": key.Type(), |
|
|
|
}).WithSourceAddr(conn.RemoteAddr()) |
|
|
|
// attempt to validate a certificate
|
|
|
|
cert, ok := key.(*ssh.Certificate) |
|
|
|
if !ok { |
|
|
|
// try fall back pubkey check
|
|
|
|
return nil, fmt.Errorf("ssh: not cert found") |
|
|
|
// return idp.pubKeyFallback(conn, key, log)
|
|
|
|
} |
|
|
|
|
|
|
|
if cert.CertType != ssh.UserCert { |
|
|
|
return nil, fmt.Errorf("ssh: cert has type %d", cert.CertType) |
|
|
|
} |
|
|
|
if !idp.certChecker.IsUserAuthority(cert.SignatureKey) { |
|
|
|
return nil, fmt.Errorf("ssh: certificate signed by unrecognized authority") |
|
|
|
} |
|
|
|
|
|
|
|
// if err := idp.certChecker.CheckCert(conn.User(), cert); err != nil {
|
|
|
|
// return nil, err
|
|
|
|
// }
|
|
|
|
// we have a valid cert
|
|
|
|
log.Debug("booking cert authentication") |
|
|
|
// validate user esists, and is in sshUsers group
|
|
|
|
u, err := idp.LookupUser(conn.User()) |
|
|
|
if err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
g, err := idp.LookupGroup(sshUsersGroup) |
|
|
|
if err != nil { |
|
|
|
return nil, err |
|
|
|
} |
|
|
|
user := u.(staffUser) |
|
|
|
group := g.(staffGroup) |
|
|
|
if !group.group.Contains(user.user) { |
|
|
|
return nil, errors.New(fmt.Sprintf("expected %s to be a member of sshUsers", user.user.Username)) |
|
|
|
} |
|
|
|
|
|
|
|
return &ssh.Permissions{ |
|
|
|
Extensions: map[string]string{ |
|
|
|
"gate-idp": "system", |
|
|
|
"gate-obj": user.user.Username, |
|
|
|
}, |
|
|
|
}, nil |
|
|
|
} |
|
|
|
|
|
|
|
// func (idp *BookingIdentityProvider) pubKeyFallback(conn ssh.ConnMetadata, key ssh.PublicKey, log *logger2.Logger) (*ssh.Permissions, error) { //needs work
|
|
|
|
// user, err := idp.LookupUser(conn.User())
|
|
|
|
// if err != nil {
|
|
|
|
// return nil, err
|
|
|
|
// }
|
|
|
|
|
|
|
|
// group, err := idp.LookupGroup(sshUsersGroup)
|
|
|
|
// if err != nil {
|
|
|
|
// return nil, err
|
|
|
|
// }
|
|
|
|
|
|
|
|
// name := filepath.Join(user.(staff.User).Obj.HomeDir, ".ssh", "authorized_keys")
|
|
|
|
// b, err := ioutil.ReadFile(name)
|
|
|
|
// if err != nil {
|
|
|
|
// return nil, err
|
|
|
|
// }
|
|
|
|
// marshaled := key.Marshal()
|
|
|
|
// for len(b) > 0 {
|
|
|
|
// var (
|
|
|
|
// authorizedKey ssh.PublicKey
|
|
|
|
// comment string
|
|
|
|
// )
|
|
|
|
// if authorizedKey, comment, _, b, err = ssh.ParseAuthorizedKey(b); err != nil {
|
|
|
|
// break
|
|
|
|
// }
|
|
|
|
|
|
|
|
// log.WithFields(logger2.Fields{
|
|
|
|
// "authorized_key": ssh.FingerprintSHA256(authorizedKey),
|
|
|
|
// "authorized_key_type": authorizedKey.Type(),
|
|
|
|
// "authorized_key_comment": comment,
|
|
|
|
// }).Debug("checking key")
|
|
|
|
// if bytes.Equal(authorizedKey.Marshal(), marshaled) {
|
|
|
|
// return &ssh.Permissions{
|
|
|
|
// Extensions: map[string]string{
|
|
|
|
// "gate-idp": "system",
|
|
|
|
// "gate-obj": user.Login(),
|
|
|
|
// },
|
|
|
|
// }, nil
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// return nil, errors.New("key not found")
|
|
|
|
// }
|