ldap authentication

Signed-off-by: Jerome Doucerain <jerome.doucerain@bell.ca>
This commit is contained in:
Jerome Doucerain
2022-06-29 18:33:57 -04:00
parent 556db3efad
commit 111b545fc7
8 changed files with 300 additions and 12 deletions
+3 -2
View File
@@ -6,14 +6,15 @@ require (
github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 // indirect
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5
github.com/fsnotify/fsnotify v1.6.0
github.com/go-ldap/ldap/v3 v3.4.3 // indirect
github.com/gorilla/websocket v1.4.2
github.com/jpillora/ansi v1.0.2 // indirect
github.com/jpillora/backoff v1.0.0
github.com/jpillora/requestlog v1.0.0
github.com/jpillora/sizestr v1.0.0
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e
golang.org/x/net v0.0.0-20210614182718-04defd469f4e
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b // indirect
)
+10
View File
@@ -1,9 +1,15 @@
github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e h1:ZU22z/2YRFLyf/P4ZwUYSdNCWsMEI0VeyrFoI2rAhJQ=
github.com/Azure/go-ntlmssp v0.0.0-20211209120228-48547f28849e/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU=
github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 h1:axBiC50cNZOs7ygH5BgQp4N+aYrZ2DNpWZ1KG3VOSOM=
github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2/go.mod h1:jnzFpU88PccN/tPPhCpnNU8mZphvKxYM9lLNkd8e+os=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A=
github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
github.com/go-ldap/ldap/v3 v3.4.3 h1:JCKUtJPIcyOuG7ctGabLKMgIlKnGumD/iGjuWeEruDI=
github.com/go-ldap/ldap/v3 v3.4.3/go.mod h1:7LdHfVt6iIOESVEe3Bs4Jp2sHEKgDeduAhgM1/f9qmo=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/jpillora/ansi v1.0.2 h1:+Ei5HCAH0xsrQRCT2PDr4mq9r4Gm4tg+arNdXRkB22s=
@@ -18,9 +24,13 @@ github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e h1:gsTQYXdTw2Gq7RBsWvlQ91b+aEQ6bXFUngBGuR8sPpI=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 h1:tkVvjkPTB7pnW3jnid7kNyAMPVWllTNOf/qKDze4p9o=
golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+12 -7
View File
@@ -159,9 +159,13 @@ var serverHelp = `
provide a certificate notification email by setting CHISEL_LE_EMAIL.
--tls-ca, a path to a PEM encoded CA certificate bundle or a directory
holding multiple PEM encode CA certificate bundle files, which is used to
validate client connections. The provided CA certificates will be used
instead of the system roots. This is commonly used to implement mutual-TLS.
holding multiple PEM encode CA certificate bundle files, which is used to
validate client connections. The provided CA certificates will be used
instead of the system roots. This is commonly used to implement mutual-TLS.
--ldap-config, a path to a file containing the ldap authentication
configuration.
` + commonHelp
func server(args []string) {
@@ -181,6 +185,7 @@ func server(args []string) {
flags.StringVar(&config.TLS.Cert, "tls-cert", "", "")
flags.Var(multiFlag{&config.TLS.Domains}, "tls-domain", "")
flags.StringVar(&config.TLS.CA, "tls-ca", "", "")
flags.StringVar(&config.LdapConfigFile,"ldap-config","","")
host := flags.String("host", "", "")
p := flags.String("p", "", "")
@@ -322,7 +327,7 @@ var clientHelp = `
client's internal SOCKS5 proxy.
When stdio is used as local-host, the tunnel will connect standard
input/output of this program with the remote. This is useful when
input/output of this program with the remote. This is useful when
combined with ssh ProxyCommand. You can use
ssh -o ProxyCommand='chisel client chiselserver stdio:%h:%p' \
user@example.com
@@ -381,11 +386,11 @@ var clientHelp = `
may be still verified (see --fingerprint) after inner connection
is established.
--tls-key, a path to a PEM encoded private key used for client
--tls-key, a path to a PEM encoded private key used for client
authentication (mutual-TLS).
--tls-cert, a path to a PEM encoded certificate matching the provided
private key. The certificate must have client authentication
--tls-cert, a path to a PEM encoded certificate matching the provided
private key. The certificate must have client authentication
enabled (mutual-TLS).
` + commonHelp
+15 -1
View File
@@ -30,6 +30,8 @@ type Config struct {
Reverse bool
KeepAlive time.Duration
TLS TLSConfig
LdapConfigFile string
LdapConfig settings.LdapConfig
}
// Server respresent a chisel service
@@ -113,6 +115,12 @@ func NewServer(c *Config) (*Server, error) {
if c.Reverse {
server.Infof("Reverse tunnelling enabled")
}
// ldap authentication
if c.LdapConfigFile != "" {
if c.LdapConfig,err = server.LdapParseConfig(c.LdapConfigFile); err != nil {
return nil, err
}
}
return server, nil
}
@@ -177,7 +185,7 @@ func (s *Server) authUser(c ssh.ConnMetadata, password []byte) (*ssh.Permissions
// check the user exists and has matching password
n := c.User()
user, found := s.users.Get(n)
if !found || user.Pass != string(password) {
if !found || ( user.Pass != string(password) && s.config.LdapConfigFile == "" ) || ( s.config.LdapConfigFile != "" && found && settings.LdapAuthUser(user,password,s.config.LdapConfig) != nil) {
s.Debugf("Login failed for user: %s", n)
return nil, errors.New("Invalid authentication for username: %s")
}
@@ -215,3 +223,9 @@ func (s *Server) DeleteUser(user string) {
func (s *Server) ResetUsers(users []*settings.User) {
s.users.Reset(users)
}
// LdapParseConfig is validating the given ldap config
func (s *Server) LdapParseConfig(LdapConfigFile string) (settings.LdapConfig,error) {
ldapConfig,err := settings.ParseConfigFile(LdapConfigFile)
return ldapConfig,err
}
+1
View File
@@ -1,3 +1,4 @@
//go:build pprof
// +build pprof
package cos
+2 -1
View File
@@ -1,4 +1,5 @@
//+build !windows
//go:build !windows
// +build !windows
package cos
+2 -1
View File
@@ -1,4 +1,5 @@
//+build windows
//go:build windows
// +build windows
package cos
+255
View File
@@ -0,0 +1,255 @@
package settings
import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"github.com/go-ldap/ldap/v3"
)
//LdapConfig enables LDAP auth
type LdapConfig struct {
BindDN string `json:"BindDN"`
BindPassword string `json:"BindPassword"`
Url string `json:"Url"`
BaseDN string `json:"BaseDN"`
Filter string `json:"Filter"`
IDMapTo string `json:"IDMapTo"`
CA string `json:"CA"`
Insecure bool `json:"Insecure"`
}
// parse the Ldap config file
func ParseConfigFile(Configfile string) (LdapConfig, error) {
var ldapConfig LdapConfig
file, err := ioutil.ReadFile(Configfile)
if err != nil {
return ldapConfig, fmt.Errorf("Ldap config file error")
}
err = json.Unmarshal([]byte(file), &ldapConfig)
if err != nil {
return ldapConfig, fmt.Errorf("Error occured during unmarshaling ldap config file")
}
return ldapConfig, nil
}
// authenticate a user using ldap credentials
func LdapAuthUser(user *User,password []byte,ldapconfig LdapConfig) error {
log.Printf("User %s to be authenticated in LDAP",user.Name)
l, err := ConnectTLS(ldapconfig)
if err != nil {
log.Printf("Error occured during TLS connection to %s",ldapconfig.Url)
return fmt.Errorf("Error occured during TLS connection to %s",ldapconfig.Url)
}
defer l.Close()
// Normal Bind and Search
result, err := BindAndSearch(l,ldapconfig,user)
if err != nil {
log.Printf("User %s not found in LDAP",user.Name)
return fmt.Errorf("User %s not found in LDAP",user.Name)
}
userdn := result.Entries[0].DN
log.Printf("DN:%s",userdn)
if len(result.Entries) != 1 {
log.Printf("too many entries returned")
return fmt.Errorf("too many entries returned")
}
// Bind as the user to verify their password
err = l.Bind(userdn,string(password[:]))
if err != nil {
return fmt.Errorf("Bad password for user %s",user.Name)
}
return nil
}
// Ldap Connection with TLS
func ConnectTLS(ldapconfig LdapConfig) (*ldap.Conn, error) {
var tlsConf *tls.Config
// var certpool *x509.CertPool
if ldapconfig.Insecure {
tlsConf = &tls.Config{InsecureSkipVerify: true}
}
if ldapconfig.CA != "" {
certpool := x509.NewCertPool()
CAfile, err := ioutil.ReadFile(ldapconfig.CA)
if err != nil {
return nil, fmt.Errorf("Ldap CA file error")
}
certpool.AppendCertsFromPEM([]byte(CAfile))
tlsConf = &tls.Config{RootCAs: certpool}
}
l, err := ldap.DialTLS("tcp", ldapconfig.Url, tlsConf)
if err != nil {
return nil, err
}
return l, nil
}
// Normal Bind and Search
func BindAndSearch(l *ldap.Conn,ldapconfig LdapConfig,user *User) (*ldap.SearchResult, error) {
var filter string
l.Bind(ldapconfig.BindDN, ldapconfig.BindPassword)
if ldapconfig.Filter != "" {
filter = fmt.Sprintf("(&(%s)(%s=%s))",ldapconfig.Filter,ldapconfig.IDMapTo,user.Name)
} else {
filter = fmt.Sprintf("(%s=%s)",ldapconfig.IDMapTo,user.Name)
}
log.Printf("filter %s",filter)
searchReq := ldap.NewSearchRequest(
ldapconfig.BaseDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
filter,
[]string{"dn"},
nil,
)
result, err := l.Search(searchReq)
if err != nil {
log.Printf("Search Error: %s", err)
return nil, fmt.Errorf("Search Error: %s", err)
}
if len(result.Entries) > 0 {
return result, nil
} else {
return nil, fmt.Errorf("Couldn't fetch search entries")
}
}
// package main
//
// import (
// "fmt"
// "log"
//
// "github.com/go-ldap/ldap/v3"
// )
//
// const (
// BindUsername = "user@example.com"
// BindPassword = "password"
// FQDN = "DC.example.com"
// BaseDN = "cn=Configuration,dc=example,dc=com"
// Filter = "(objectClass=*)"
// )
//
// func main() {
// // TLS Connection
// l, err := ConnectTLS()
// if err != nil {
// log.Fatal(err)
// }
// defer l.Close()
//
// // Non-TLS Connection
// //l, err := Connect()
// //if err != nil {
// // log.Fatal(err)
// //}
// //defer l.Close()
//
// // Anonymous Bind and Search
// result, err := AnonymousBindAndSearch(l)
// if err != nil {
// log.Fatal(err)
// }
// result.Entries[0].Print()
//
// // Normal Bind and Search
// result, err = BindAndSearch(l)
// if err != nil {
// log.Fatal(err)
// }
// result.Entries[0].Print()
// }
//
// // Ldap Connection with TLS
// func ConnectTLS() (*ldap.Conn, error) {
// // You can also use IP instead of FQDN
// l, err := ldap.DialURL(fmt.Sprintf("ldaps://%s:636", FQDN))
// if err != nil {
// return nil, err
// }
//
// return l, nil
// }
//
// // Ldap Connection without TLS
// func Connect() (*ldap.Conn, error) {
// // You can also use IP instead of FQDN
// l, err := ldap.DialURL(fmt.Sprintf("ldap://%s:389", FQDN))
// if err != nil {
// return nil, err
// }
//
// return l, nil
// }
//
// // Anonymous Bind and Search
// func AnonymousBindAndSearch(l *ldap.Conn) (*ldap.SearchResult, error) {
// l.UnauthenticatedBind("")
//
// anonReq := ldap.NewSearchRequest(
// "",
// ldap.ScopeBaseObject, // you can also use ldap.ScopeWholeSubtree
// ldap.NeverDerefAliases,
// 0,
// 0,
// false,
// Filter,
// []string{},
// nil,
// )
// result, err := l.Search(anonReq)
// if err != nil {
// return nil, fmt.Errorf("Anonymous Bind Search Error: %s", err)
// }
//
// if len(result.Entries) > 0 {
// result.Entries[0].Print()
// return result, nil
// } else {
// return nil, fmt.Errorf("Couldn't fetch anonymous bind search entries")
// }
// }
//
// // Normal Bind and Search
// func BindAndSearch(l *ldap.Conn) (*ldap.SearchResult, error) {
// l.Bind(BindUsername, BindPassword)
//
// searchReq := ldap.NewSearchRequest(
// BaseDN,
// ldap.ScopeBaseObject, // you can also use ldap.ScopeWholeSubtree
// ldap.NeverDerefAliases,
// 0,
// 0,
// false,
// Filter,
// []string{},
// nil,
// )
// result, err := l.Search(searchReq)
// if err != nil {
// return nil, fmt.Errorf("Search Error: %s", err)
// }
//
// if len(result.Entries) > 0 {
// return result, nil
// } else {
// return nil, fmt.Errorf("Couldn't fetch search entries")
// }
// }