Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
6c1f269
ldap authentication
jdoucerain Jun 29, 2022
4bc9fc4
new --ldap-config in README.md
jdoucerain Jun 29, 2022
6c50813
remove comments in ldap.go
jdoucerain Jun 29, 2022
5b77520
add user in debugging log when too many entries
jdoucerain Jun 29, 2022
79c0a67
ca file debug
jdoucerain Jun 29, 2022
6f7bc5d
Merge branch 'jpillora:master' into LDAP-authentication
jdoucerain Oct 18, 2022
00836b1
allow combination of both LDAP and local authentication and give ldap…
jdoucerain Oct 18, 2022
f7cdb96
a bit improved version of the password validation keeping the ability…
jdoucerain Oct 19, 2022
7c0e741
Changes as recommended by J.Pillora
jdoucerain Nov 25, 2022
2d7c977
markdown adjustments
jdoucerain Nov 25, 2022
8f96052
markdown adjustments
jdoucerain Nov 25, 2022
111b545
ldap authentication
jdoucerain Jun 29, 2022
505ba02
new --ldap-config in README.md
jdoucerain Jun 29, 2022
ba54e8d
remove comments in ldap.go
jdoucerain Jun 29, 2022
92b20ba
add user in debugging log when too many entries
jdoucerain Jun 29, 2022
4708fe1
ca file debug
jdoucerain Jun 29, 2022
40da157
allow combination of both LDAP and local authentication and give ldap…
jdoucerain Oct 18, 2022
69a8ce0
a bit improved version of the password validation keeping the ability…
jdoucerain Oct 19, 2022
cfb1d00
Changes as recommended by J.Pillora
jdoucerain Nov 25, 2022
659c06d
markdown adjustments
jdoucerain Nov 25, 2022
3e05ec5
markdown adjustments
jdoucerain Nov 25, 2022
62920af
Merge branch 'LDAP-authentication' of https://github.com/jdoucerain/c…
jdoucerain Nov 25, 2022
b66e317
markdown adjustments
jdoucerain Nov 25, 2022
6b87e3f
MD again
jdoucerain Nov 25, 2022
3f92a77
Merge branch 'master' of https://github.com/jpillora/chisel into LDAP…
jdoucerain Jul 7, 2023
3a6f13c
update README.md with features from master upstream branch
jdoucerain Jul 7, 2023
8786463
fix unreachable code
jdoucerain Jul 7, 2023
fc5387c
exclusion between local passwords and LDAP authentication
jdoucerain Jul 10, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
file README.md
======
# Chisel

[![GoDoc](https://godoc.org/github.com/jpillora/chisel?status.svg)](https://godoc.org/github.com/jpillora/chisel) [![CI](https://github.com/jpillora/chisel/workflows/CI/badge.svg)](https://github.com/jpillora/chisel/actions?workflow=CI)
Expand Down Expand Up @@ -179,6 +181,22 @@ $ chisel server --help
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 JSON configuration file, which defines settings used to
connect to a remote LDAP server for authenticating users. once configured, user
passwords will be validated against the configured LDAP server.
here is an example of an ldap-config file
{ "BindDN": "CN=ldapUser,OU=Users,OU=example,DC=EXAMPLE,DC=COM",
"BindPassword": "ldapUserPassword",
"Url": "example.com:636",
"BaseDN": "OU=Users,OU=example,DC=EXAMPLE,DC=COM",
"Filter": "(&(objectClass=person)(objectClass=user))",
"IDMapTo": "sAMAccountName",
"CA": "",
"Insecure": true }

Comment thread
jdoucerain marked this conversation as resolved.



--pid Generate pid file in current working directory

-v, Enable verbose logging
Expand Down Expand Up @@ -300,6 +318,9 @@ $ chisel client --help
--hostname, Optionally set the 'Host' header (defaults to the host
found in the server url).

--sni, Override the ServerName when using TLS (defaults to the
hostname).

--tls-ca, An optional root certificate bundle used to verify the
chisel server. Only valid when connecting to the server with
"https" or "wss". By default, the operating system CAs will be used.
Expand Down Expand Up @@ -407,3 +428,5 @@ Since WebSockets support is required:
## License

[MIT](https://github.com/jpillora/chisel/blob/master/LICENSE) © Jaime Pillora

======
5 changes: 3 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -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=
Expand All @@ -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=
Expand Down
17 changes: 17 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,22 @@ var serverHelp = `
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 JSON configuration file, which defines settings used to
connect to a remote LDAP server for authenticating users. once configured, user
passwords will be validated against the configured LDAP server.
here is an example of an ldap-config file
{ "BindDN": "CN=ldapUser,OU=Users,OU=example,DC=EXAMPLE,DC=COM",
"BindPassword": "ldapUserPassword",
"Url": "example.com:636",
"BaseDN": "OU=Users,OU=example,DC=EXAMPLE,DC=COM",
"Filter": "(&(objectClass=person)(objectClass=user))",
"IDMapTo": "sAMAccountName",
"CA": "",
"Insecure": true }



` + commonHelp

func server(args []string) {
Expand All @@ -181,6 +197,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", "", "")
Expand Down
67 changes: 51 additions & 16 deletions server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package chserver
import (
"context"
"errors"
"fmt"
"log"
"net/http"
"net/http/httputil"
Expand All @@ -22,14 +23,16 @@ import (

// Config is the configuration for the chisel service
type Config struct {
KeySeed string
AuthFile string
Auth string
Proxy string
Socks5 bool
Reverse bool
KeepAlive time.Duration
TLS TLSConfig
KeySeed string
AuthFile string
Auth string
Proxy string
Socks5 bool
Reverse bool
KeepAlive time.Duration
TLS TLSConfig
LDAPConfigFile string
LDAPConfig settings.LDAPConfig
}

// Server respresent a chisel service
Expand Down Expand Up @@ -69,6 +72,10 @@ func NewServer(c *Config) (*Server, error) {
if c.Auth != "" {
u := &settings.User{Addrs: []*regexp.Regexp{settings.UserAllowAll}}
u.Name, u.Pass = settings.ParseAuth(c.Auth)
if c.LDAPConfigFile != "" && u.Pass != "" {
// we should not have local password based authentication with LDAP
log.Fatal("No local password based authentication in combination with LDAP")
}
if u.Name != "" {
server.users.AddUser(u)
}
Expand Down Expand Up @@ -113,6 +120,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
}

Expand Down Expand Up @@ -177,14 +190,30 @@ 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) {
s.Debugf("Login failed for user: %s", n)
return nil, errors.New("Invalid authentication for username: %s")
}
// insert the user session map
// TODO this should probably have a lock on it given the map isn't thread-safe
s.sessions.Set(string(c.SessionID()), user)
return nil, nil

if !found {
return nil, errors.New("user not found")
}
if string(password) == "" {
return nil, errors.New("user password not set")
}
if user.Pass == string(password) && s.config.LDAPConfigFile == "" {
// local authentication successful and not combined with LDAP
// insert the user session map
s.sessions.Set(string(c.SessionID()), user)
return nil, nil
}
if s.config.LDAPConfigFile != "" && user.Pass == "" {
if err := settings.LDAPAuthUser(user, password, s.config.LDAPConfig); err != nil {
return nil, fmt.Errorf("user ldap auth failed: %w", err)
}
// ldap authentication successful and no local password used
// insert the user session map
s.sessions.Set(string(c.SessionID()), user)
return nil, nil
}
Comment thread
jdoucerain marked this conversation as resolved.
return nil, errors.New("user auth failed")

}

// AddUser adds a new user into the server user index
Expand Down Expand Up @@ -215,3 +244,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 change: 1 addition & 0 deletions share/cos/pprof.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build pprof
// +build pprof

package cos
Expand Down
3 changes: 2 additions & 1 deletion share/cos/signal.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//+build !windows
//go:build !windows
// +build !windows

package cos

Expand Down
3 changes: 2 additions & 1 deletion share/cos/signal_windows.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
//+build windows
//go:build windows
// +build windows

package cos

Expand Down
133 changes: 133 additions & 0 deletions share/settings/ldap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package settings
Comment thread
jdoucerain marked this conversation as resolved.

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 for user %s", user.Name)
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

if ldapconfig.Insecure {
tlsConf = &tls.Config{InsecureSkipVerify: true}
}

if ldapconfig.CA != "" {
log.Printf("CA file %s", ldapconfig.CA)
certpool := x509.NewCertPool()
CAfile, err := ioutil.ReadFile(ldapconfig.CA)
if err != nil {
log.Printf("LDAP CA file error")
return nil, fmt.Errorf("LDAP CA file error")
}
certpool.AppendCertsFromPEM([]byte(CAfile))
tlsConf = &tls.Config{RootCAs: certpool}
log.Printf("CA file %s loaded", ldapconfig.CA)
}

l, err := ldap.DialTLS("tcp", ldapconfig.Url, tlsConf)
if err != nil {
log.Printf("TLS error: %s", err)
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")
}
}